Callbacks, Async Await y promises en Javascript
Esta es una sección donde se explicará de forma sencilla y detallada; tres cosas fundamentales que debes de saber si desarrollas en js, sin importar si te dedicas al backend con node, o si te dedicas a frontend tanto con vue, React o Angular, es indispensable tener en claro estas tres características que nos provee js.
Callbacks
Empecemos definiendo primero ¿Qué es un callback? según la documentación de mozilla, lo explica de esta manera
Una función de callback es una función que se pasa a otra función como un argumento, que luego se invoca dentro de la función externa para completar algún tipo de rutina o acción.
Es bastante probable que más de alguna vez ya hayas usado callbacks sin darte cuenta. Si has usado la función setTimeout(); has usado una función callback. Veamos una personalizada
Tenemos el siguiente ejemplo
Detente un momento, y piensa qué salida nos mostrara en consola.
Básicamente lo que tenemos acá, es una función flecha, que recibe por parámetro un id, y un callback, el callback es ejecutado internamente en la función getUserByID y este callback debe de ser pasado como parámetro al momento que ejecutemos getUserByID.
La salida en consola, sería la siguiente
This is a user { name: ‘Kevin Mendez’, id: 1 }
Bien, hemos ejecutado nuestro primer callback, pero este callback puede darnos un error por X o Y motivo, así que los callback nos da la posibilidad de poder controlar dicho error; para nuestro ejemplo, vamos a simular que el id del usuario no existe.
Para saber cuándo algo ha fallado dentro de nuestro callback, siempre es necesario que la funcion callback reciba como primer parametro el error devuelto; esto nos ayuda a tener un control del error; pero ¿cómo saber si todo ha ido bien? para ello, la funcion callback nos obliga a pasar siempre el parámetro errors como null; ahora si te preguntas, porqué tenemos que pasar siempre null como primer parámetro aun a pesar de que todo se ha ejecutado como esperamos; es porque si no pasas como primer parámetro null, la función, tomará como error el primer parámetro y la salida en consola seria la siguiente:
{ name: ‘Kevin Mendez’, id: 1 }
Al parecer, todo funciona bien, pero si te preguntas sobre los inconvenientes que puedes tener sobre los callbacks. Veamoslo en el siguiente ejemplo
Para tener más claro sobre los inconvenientes con los que te puedes encontrar con los callbacks; vamos a simular que hemos traído datos de la db.
Ojo con ese array de objeto llamado sales, ¿ves que solo tiene 2 objetos, y employed 3?
Ahora es probable que no veas cuáles podrían ser los problemas con los que te podes encontrar. Has en cuenta que la función callback(null, some) ya la has ejecutado; es decir, en consola me ejecutaría dos veces el console.log, y no seria nada raro,que la lógica de nuestro callback sea extensa, y ejecutemos la misma función más de una vez. En nuestro caso, facilmente lo identificamos, porque nos muestra dos veces el mensaje en consola, pero ¿y si no imprimimos nada en consola?. ;)
Hagamos una función que me relacione las ventas por empleado.
Tal cual esta el código, veríamos una salida en consola de la siguiente manera
No sales found for user Marta
Si sigues el código, prueba poniendo 1 en lugar del 3, y veremos una salida como la siguiente
{ name: ‘Kevin’, sales: ‘$200’ }
¿Qué hicimos? Pues sencillo, creamos una función que nos devuelve las ventas por empleado; pero no ejecutamos primero una función y luego otra, sino, que hemos hecho un desencadenamiento de callbacks.
1- primero hemos obtenido un empleado, lo hemos validado que exista con su respectivo callback
2- Hemos creado una función, para obtener las ventas por empleado, donde como primer parámetro, recibe el empleado encontrado del paso 1, seguido de la validación callback para dicha función.
Sencillo, ejecutamos un callback, seguido de la ejecución de otro callback dentro del callback ejecutado anteriormente (vaya rollo este).
En este ejemplo sencillo, es mantenible aun el código, se entiende, pero imagínate que nos piden que también obtengamos información por ventas, es decir, ejecutar otro callback dentro de la función getSales; se nos hace complicado, tanto la lectura del código, como el mantenimiento a futuro.
Más adelante veremos la solución a dicha problemática.
Promises
Empecemos definiendo ¿Qué es una promise en javascript? según la documentación de mozilla la describe así:
El objeto
Promise
(Promesa) es usado para computaciones asíncronas. Una promesa representa un valor que puede estar disponible ahora, en el futuro, o nunca.
La sintaxis es la siguiente
new Promise( /* ejecutor */ function(resolver, rechazar) { ... } );
Fíjate detalladamente esa “funcion(resolver, rechazar)” ¿qué crees que sea? — Exacto, es un callback ;) y ya que entendemos de qué van los callbacks, ya fácilmente vamos a entender como funciona una promise.
Las promesas nos dan la flexibilidad de poder trabajar con procesos tanto síncronos, como asíncronos, y son muy utilizadas en el desarrollo de software; lo grandioso de ello, es que luego de que finalice el proceso que se estaba ejecutando, podemos realizar otra tarea, como por ejemplo; lanzar otra promise; o supongamos que haremos una petición http a nuestra API y luego de que obtengamos los datos, estos datos requiera que los mutemos. Es en estos casos donde las promesas nos muestran los beneficios.
Empecemos con un ejemplo sencillo; siempre tomando como base los dos objetos antes creados de employed y sales.
El callback reject; se ejecutará siempre que encuentre un error, independientemente de cuál sea, y el resolve, es el callback que se ejecuta, siempre que todo salga bien.
Además observemos la forma en que se llama la función; la parte de .then es la que se ejecuta cuando todo ha salido bien, y el .catch cuando algo ha salido mal; es decir, .then, la ejecuta el callback resolve, y .catch la ejecuta el callback reject.
Si en la función getEmployedById pasamos como parametro el 1, nos da una solida en consola de la siguiente manera:
{ id: 1, name: 'Kevin', lastname: 'Mendez', age: 23 }
¿qué pasa si le pasamos por parámetro el id de un usuario que no existe? como por ejemplo, el 10 — en este caso nos dará una salida como la siguiente
The employed 10 doesn't exist
Esto es debido a que no ha encontrado un empleado con el id 10, por ende, en la validación hecha con el if, ejecutamos el reject, que como antes mencione, se ejecuta cuando algo ha fallado, caso contrario, ejecuta en el callback resolve, y esto nos indica, que sí encontramos un empleado con el id 1
Hemos hecho, básicamente, el mismo ejercicio de callbacks con promises, aunque, hasta el momento, no hemos solventado la problemática de la accidentario a la derecha, y la problemática de legibilidad en nuestro código, y la dificultad de darle mantenimiento a nuestro código a futuro.
Para ello, existe una ventaja aun mayor que nos provee el uso de promises, y son las promises en cadena, es decir, retornar una promise dentro del .then, o .catch, dependiendo de nuestras necesidades.
Veamos un ejemplo:
Observa como controlamos los errores en un solo catch, sin importar que tantas promises ejecutemos, aparte de que el codigo es más limpio, no evitamos tener una fea identacion de codigo a la derecha, y nos es más fácil poder darle mantenimiento a futuro.
¿Qué pasaría si ingresamos un empleado existente? ¿Qué nos diera de salida en consola?
Si pasamos por parametro el 1 en la funcion getEmployedById nos diera la siguiente salida:
{ employed: 'Kevin', sales: '$200' }
¿y qué nos devuelve, si ingresamos un usuario que si existe, pero que no tiene ventas? — En este caso el empleado 3
Nos diera como salida en consola.
The employed Marta doesn't have sales
¿y qué pasaría si ingresamos un empleado que no existe? — En este caso el empleado 10
Nos diera como salida en consola:
The employed 10 doesn't exist
Es de tener en cuenta que las promises son una características del ECMAScript 6 y en mi caso, que estoy ejecutando los ejemplos con nodemon, funciona a la perfección.
Async Await
Empecemos definiendo primero qué es async await; ya que según la documentación de mozilla es
La declaración de función
async
define una función asíncrona, la cual devuelve un objetoAsyncFunction
.
Y consta de la siguiente sintaxis:
async function name([param[, param[, ... param]]]) {
statements
}
¿cómo funcionan las async await?
“Cuando se llama a una función
async
, esta devuelve un elementoPromise
. Cuando la funciónasync
devuelve un valor,Promise
se resolverá con el valor devuelto. Si la funciónasync
genera una excepción o algún valor,Promise
se rechazará con el valor generado.Una función
async
puede contener una expresiónawait
, la cual pausa la ejecución de la función asíncrona y espera la resolución de laPromise
pasada y, a continuación, reanuda la ejecución de la funciónasync
y devuelve el valor resuelto.”
El async await nos provee la facilidad de tener un codigo aun mas visible, que es lo que en la mayoria de los casos, el programador anda buscando.
Async await, nos convierte una función normal, sencilla de toda la vida, en una función asíncrona, debido a que en javascript, no existe el código asíncrono; ya que siempre se ejecuta todo el código, sin esperar que alguna operación (que demore algunos segundos en terminar) finalice; por eso a partir del ECMAScript 7, se incorpora este funcionalidad.
¿Cómo es interpretada?
Tratemos de explicarlo mediante código con el siguiente ejemplo, siempre tomando como base, lo ya antes trabajado de los callbacks y promises
Supongamos que tenemos la siguiente función
Tenemos una función, normal y corriente que se ejecuta, obtiene el empleado 1, y nos lo muestra en consola
Si deseamos pasar dicha función a una función async await, es tan sencillo como anteponerle la palabra reservada async antes de recibir los parámetros.
Traduciendo dicha función a la definición dada por mozilla tendríamos lo siguiente
Por el simple hecho de anteponer la palabra reservada async resumimos una promise como se muestra en la ultima función getEmployedById = (id)….
Es por eso, que el async await, nos facilita, y reduce nuestro código; convirtiendo una función cualquiera, en una promise
Al momento de definir una función como async, ya tenemos disponible el .then y .catch
Si te preguntas por los errores, sobre cómo capturarlos, ya por defecto, sin tener que hacer nada, async captura cualquier error dado, y nos lo imprimira como una excepción.
En nuestro ejemplo, si colocamos el empleado 10 (que no existe) javascript, nos devolverá un valor undefined
¿Pero y si quiero personalizar ese undefined? — que nos diga tipo employed not found por ejemplo
Para ello, existe una funcionalidad de javascript: throw new Error(‘’) — dejaremos esta característica para una futura publicación ya que es más que todo cuando se usa node; aunque se puede seguir usando en el frontend.
Hasta el momento solo hemos hablado sobre el async entonces ¿qué pasa con el await?
Es de tener en cuente que para usar await siempre, pero siempre debe de estar dentro de una función async; recordarlo siempre, porque suele pasar que uno se pregunte por qué no nos funciona nuestro código, cuando hemos olvidado el async.
Cuando usamos await, hace que nuestras respuestas se tomen como un trabajo sincrono, es decir, supongamos que tenemos nuestra app frontend, y desde el frontend, hacemos una petición http a nuestra api; y vamos a tener una cantidad exorbitante de datos, y supongamos que tarda un aproximado de 5 segundos; esta tarea, esperara la data para imprimirla en nuestro frontend, ¿cómo? veamoslo en código.
En este ejemplo, estamos emulando 5s de retraso, es decir, que el resolve(employedtest) no se ejecutará hasta pasados los 5 segundos, y que el await hace espera de esos 5 segundos.
¿qué pasaría si quitamos el valor de await? ¿Qué nos mostraría en consola? — espero que puedan comprobar por ustedes mismos el resultado
“La finalidad de las funciones
async
/await
es simplificar el comportamiento del uso síncrono de promesas y realizar algún comportamiento específico en un grupo dePromises
. Del mismo modo que lasPromises
son semejantes a las devoluciones de llamadas estructuradas,async
/await
se asemejan a una combinación de generadores y promesas”
Post inspirado en Fernando Herrera