Bienvenidos sean a este post, hoy veremos un tema muy particular.
En el post anterior mencionamos que el asincronismo nacio para poder trabajar con las posibles demoras que pueden ocurrir cuando trabajamos con internet. En el post anterior vimos que una posibilidad era mediante callbacks pero a medida que nuestro codigo continue creciendo vamos a necesitar mas y mas de estas funciones. Lo cual no solo nos llevara a incrementar nuestro codigo sino que lo volvera dificil de leer para depurarlo y al mismo tiempo de mantenerlo. Analicemos el siguiente ejemplo:
import * as fs from "fs";
fs.readFile("./test1.txt", (err, data) => {
if (err) {
console.log(`an error occurred : ${err}`);
} else {
console.log(`test1.txt contents : ${data}`);
fs.readFile("./test2.txt", (err, data) => {
if (err) {
console.log(`an error occurred : ${err}`);
} else {
console.log(`test2.txt contents : ${data}`);
fs.readFile("./test3.txt", (err, data) => {
if (err) {
console.log(`an error occurred : ${err}`);
} else {
console.log(`test3.txt contents : ${data}`);
}
})
}
})
}
});
En este ejemplo utilizaremos a la libreria fs siendo nuestra primera accion su importacion, y luego leeremos para ver el contenido de un archivo y para ello usaremos una funcion de callback la cual tendra dos argumentos: el primero almacena por si surge algun error (p.e el archivo no existe) y el otro para el contenido. Lo siguiente sera usar if por si almacenamos un valor en err y lo notificamos y el else es para mostrar el contenido. Pero dentro de este volveremos a usar readFile para leer otro archivo y volvemos a aplicar el mismo callback y el mismo condicional. Nuevamente en else volvemos a repetir lo mismo para otro archivo y lo mismo para un cuarto archivo. Este codigo no solo es largo sino dificil de leer y ante un eventual error se nos dificultara determinar cual es el inconveniente.
Para resolver este inconveniente entran en accion lo que da titulo a este post. Para entender el concepto vamos a tomar el codigo anterior y adaptarlo a promises:
fs.promises.readFile("./test1.txt")
.then((value) => {
console.log(`ps test1.txt read : ${value}`);
return fs.promises.readFile("./test2.txt");
}).then((value) => {
console.log(`ps test2.txt read : ${value}`);
return fs.promises.readFile("./test3.txt");
}).then((value) => {
console.log(`ps test3.txt read : ${value}`);
})
.catch((error) => {
console.log(`an error occurred : ${error}`);
});
Tomamos a fs le aplicamos un namespace promises y luego desde este usaremos a readFile, el cual es similar al anterior pero aplicando promesas o promises, y le pasamos el primer archivo. En este caso en lugar de usar los if usamos a then y aqui aplicaremos una funcion de callback donde solo recibe un dato que es el contenido del archivo. En este caso lo mostraremos para luego devolver una promesa de lectura del siguientte archivo. Esta promesa es interceptada por el siguiente donde volvemos a repetir lo mismo para leer el nuevo archivo y de nuevo con otro then lo procesamos. Por ultimo tenemos un catch donde lo usaremos por si surgio algun error que impidio la lectura y la notificaremos. Logramos exactamente lo mismo pero con un codigo mucho mas simple. Antes de continuar les debo aclarar que las promesas siempre devuelven dos estados:
- Resolved, cuando se obtuvo la informacion
- Rejected, cuando ocurrio un error y fue rechazado
Aqui es donde trabajan tanto then como catch. Cuando realizamos una promesa y esta nos devuelve el estado Resolved le estamos diciendo al programa que hubo exito en el procesamiento y por lo tanto mediante then trabajaremos con la informacion recibida. En cambio cuando devuelve un estado Rejected sea cual sea el motivo hara que entre en accion el catch y permitiendo que podamos manejar ese error en nuestro codigo.
Si bien este es la forma de aplicar promesas, en realidad lo estamos haciendo desde una libreria de terceros, tambien podemos crear nuestras propias promesas para nuestros eventos. Una promesa no deja de ser una instancia de la clase Promises para lo cual el constructor requiere una firma de funcion que acepte dos funciones de callback. Estas por convencion se las denomina como resolve y reject pero podemos denominarlas como creamos necesarias. Analicemos el siguiente codigo:
function promesaDemorada(
resuelto: () => void,
rechazado: () => void) {
function despues() {
resuelto();
}
setTimeout(despues, 1000);
}
Esta funcion es muy similar a respuestaDemorada que vimos en el post anterior, aqui tenemos dos argumentos para recibir los estados de las promesas. En el bloque definimos una funcion que devuelve el estado resuelto y por ultimo tenemos un setTimeout para llamar a esta funcion. En el post anterior en lugar de trabajar con el estado resuelto lo haciamos conn la funcion callback pasado como argumento. Con esto podemos crear un objeto de promesa, veamos un ejemplo:
function respuestaPromesa(): Promise<void> {
return new Promise<void>(promesaDemorada);
}
Esta funcion devuelve un objeto de tipo Promise y para que funcione utilizamos la funcion anterior que posee los dos argumentos para manejar los dos estados de la promesa. Pero en la practica estos dos codigos se utilizan como uno solo en lugar de separados. Veamos como se combinan:
function promesaDemorada(): Promise<void> {
return new Promise<void>(
(
resuelto: () => void,
rechazado: () => void
) => {
function despues() {
resuelto();
}
setTimeout(despues, 1000);
}
);
}
En este caso tomamos la primer funcion y ahora devolvera un tipo Promise, un tema que no mencionamos es que Promise usa un tipo void porque es el tipo de dato que devuelven las funciones encargadas de manejar sus estados, luego usaremos el return para devolver un objeto de este tipo. Aqui definiremos una funcion la cual posee dos argumentos que seran los encargados de manejar los estados. Dentro del bloque de la funcion agregamos la funcion despues que vimos anteriormente y establecemos el setTimeout. Es la perfecta union entre las dos funciones, lo cual nos habilita para poder utilizarlo de la siguiente manera:
promesaDemorada().then(() => {
console.log("promesa demorada devuelta");
});
Aqui llamamos a la funcion que mediante then devolvera el resultado de la promesa. Ya tenemos la forma de manejar una respuesta pero como manejamos un error? como mencionamos en los primeros ejemplos lo haremos atraves de catch pero este toma el valor de la callback que usemos para procesar el rejected (rechazado). Vamos a tomar el codigo anterior y hagamos la siguientes modificaciones:
function errorPromesa(): Promise<void> {
return new Promise<void>(
(
resuelto: () => void,
rechazado: () => void
) => {
console.log("2. llamando reject()");
rechazado();
}
);
}
console.log("1. llamando errorPromesa");
errorPromesa().then(() => {})
.catch(() => { console.log("3. error capturado")});
Tomamos la funcion de promesaDemorada y le cambiamos el nombre. La otra modificacion fue la eliminacion de demora y aqui directamente llamamos a rechazado. Luego llamaremos a esta funcion donde primero aplicaremos primero un then y luego un catch para notificar el error. Observen que hemos agregado varias notificaciones de cada paso que realizamos. Compilemos y veamos la salida:
$ node async.js
1. llamando errorPromesa
2. llamando reject()
3. error capturado
$
Si bien nosotros forzamos este error pero es para ver como catch captura siempre el error si existe y recuerden que siempre llamando a la funcion que contiene al error sera lanzado el catch.
Como dijimos las promesas devuelven dos estados, uno para cuando ocurre un error y otro cuando se realizo. Por lo general, salvo algunas excepciones, obtendremos un resultado. En los ejemplos anteriores cuando definiamos a la promesa le indicamos que devuelva un void, porque sabiamos que no devolviamos ningun valor, pero cuando sabemos que debe manejar un tipo de dato se lo debemos especificar. Analicemos el siguiente codigo:
function promesa(err: boolean): Promise<string> {
return new Promise<string>(
(
resuelto: (valor: string) => void,
rechazado: (errCodigo: number) => void
) => {
if (err) {
rechazado(101);
}
resuelto("mensaje de resolucion");
}
);
}
console.log("1. llamando a promesa");
promesa(false)
.then((valor: string) => { console.log("valor: " + valor); })
.catch((errCodigo: number) => {
console.log("error code: " + errCodigo);
});
La estructura de la funcion es similar a los vistos anteriormente pero en esta ocasion agregaremos algunas modificaciones. La primera es que ahora recibe un argumento. La segunda es que la promesa ahora devuelve un tipo string. Otra modificacion es que las funciones encargadas de resolver los estados reciben un argumento. En el bloque tenemos un condicional donde verifica si err posee un valor, en caso de ser verdad envia el estado rechazado con un valor. En caso de no cumplirse envia un resuelto con ese mensaje. Notificamos el llamado a promesa y luego lo realizamos con el valor false. Despues tenemos un then que recibira un valor y lo mostraremos en pantalla, lo siguiente es el catch donde recibira un numero y en caso de error lo nottificaremos. Compilemos y veamos la salida:
$ node async.js
1. llamando a promesa
valor: mensaje de resolucion
$
Como pueden ver funciono correctamentte porque al ser false no se cumple la condicion y procede a notificarnos que esta todo bien. Si cambian el valor de false a true nos sucedera lo siguiente:
$ node async.js
1. llamando a promesa
error code: 101
$
Obtuvimos el dato enviado y lo pudimos pasar al catch tal como vimos antes con el then. Con esto ya tenemos las promesas bastante comentadas pero hay algunas reglas que debemos tener en cuenta:
- Una promesa es un objeto que necesita de una funcion para ser pasada como parte de su constructor. Esta funcion puede ser con nombre o anonima, por lo general es anonima.
- Esta funcion tiene una firma muy particular. Esta requiere dos parametros que generalmente son callbacks y son para procesar el estado resolved y rejected de la promesa. Usualmente se los denomina como resolve y reject pero no es obligatorio.
- Solo puede haber uno solo para resolve. Este recibe datos del tipo que se establecieron al momento de crear la promesa
- Solo puede haber uno solo para reject. Este no necesariamente tiene que ser del mismo tipo que resolve.
- Las funciones resolve y reject siempre deben devolver void
Siguiendo estas simples reglas siempre podran manejar de manera correcta todo lo relacionado al codigo asincronico. Para ir finalizando vamos a analizar el ultimo codigo:
interface IConexion {
server: string;
port: number;
}
interface IError {
codigo: number;
mensaje: string;
}
interface ILineaDatos {
id: number;
nombre: string;
usuario: string;
}
function promesaCompleja(
conexion: IConexion, clave: string
) : Promise<ILineaDatos[]> {
return new Promise<ILineaDatos[]>(
(
resuelto: (resultado: ILineaDatos[]) => void,
rechazado: (resultado: IError) => void
} => {
// Aqui haremos la conexion
// y procesremos la info recibida
}
);
)
promesaCompleja({ server: "tinchicus.com", port: 80 },"abcd")
.then((filas: ILineaDatos[]) => {
// instrucciones para procesar la info recibida
})
.catch((error: IError) => {
// aqui deben procesar al error
});
Primero definiremos tres interfaces, la primera sera para recibir los datos de conexion. La segunda es para almacenar el codigo de error y su mensaje. La ultima interfaz sera la que recibe la informacion buscada en el servidor. La funcion que sera la promesa para manejarlo recibira dos argumentos. El primero sera la conexion y del tipo de la interfaz creada para ello, el otro dato es una clave de acceso o token. La promesa que recibe sera del tipo de la interfaz con los datos recibidos. La funcion encargada de tomar el proceso exitoso sera del mismo tipo que la promesa, en cambio la del fallo utilizara a la interfaz de error. En el bloque tendremos un codigo para conectarse al servidor con los datos recibidos, buscar la informacion y procesarla. Luego tenemos el llamado a esta funcion donde pasaremos los datos de conexion y una clave. Tenemos un then para recibir los datos procesados y hacer lo que debamos hacer y luego el catch para procesar el error en caso de que suceda. Las promesas se han convertido en algo muy difundido principalmente en librerias derivadas de javascript como son node.js o jquery.
Esto es especialmente asi porque estos usualmente no se quedan solamente del lado del cliente sino que manipulan informacion del servidor y por ende necesitamos poder manipular las demoras y/o errores que pueden suceder en una conexion. Como dije lo veremos mas seguido de lo que creeran.
En resumen, hoy hemos promesas, que son, para que sirven, como se utilizan, sus distintas particularidades y algunos ejemplos para ver como trabajan, espero les haya sido de utilidad sigueme en tumblr, Twitter o Facebook para recibir una notificacion cada vez que subo un nuevo post en este blog, nos vemos en el proximo post.


Donación
Es para mantenimento del sitio, gracias!
$1.50
