En esta página:
Hace algunos años atrás, los callbacks eran la única forma en que podíamos lograr la ejecución de código asíncrono en JavaScript. Hubo pocos problemas con los callbacks, sin embargo el problema más notable fue los callback hell y en ES6 (ECMAScript 6) se introdujo las Promises que dan solución a los callback hell y en este Post te enseñare como se puede convertir un callback a Promises en Node JS 13.7.0
Antes de continuar con este Post te invito a escuchar el Podcast: “Donde buscar ayuda sobre Programación”:
Spotify:
Sound Cloud:
Bien ahora continuemos con el Post: Convertir Callbacks a Promises en Node JS 13.7.0.
En ES6 (ECMAScript 6) las keywords async/await ofrecen una experiencia mejorada y nuevos enfoques para el Desarrollo con JavaScript.
Que es un Callback ?
Es un argumento de una determinada función que resulta ser una función en sí misma. Si bien podemos crear cualquier función para aceptar otra función, las devoluciones de llamada se utilizan principalmente en operaciones asíncronas.
JavaScript es un Lenguaje interpretado que solo puede procesar una línea de código a la vez. Algunas tareas pueden tardar mucho tiempo en completarse, como descargar o leer un archivo grande. JavaScript envía estas tareas de larga duración a un proceso diferente en el navegador o en el entorno Node JS. De esa manera, no bloquea la ejecución del resto del código.
Las funciones asíncronas aceptan una función callback que se ejecutará cuando el programa lea con éxito un archivo de nuestra computadora. Por ejemplo a continuación usaré un archivo de texto llamado archivo.txt que contiene el texto Hola Nube Colectiva ! y escribiré un código simple en Node JS dentro de un archivo llamado index.js para leer el archivo de texto archivo.txt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
const fs = require('fs'); fs.readFile('./archivo.txt', 'utf-8', (err, data) => { if (err) { // Manejo de errores console.error(err); return; } // Salida en consola console.log(data); }); for (let i = 0; i < 5; i++) { console.log(i); } |
Ahora ejecuto el comando node index y obtengo:
1 2 3 4 5 6 7 8 9 10 |
$ node index 0 1 2 3 4 Hola Nube Colectiva ! |
Puedes ver en la salida anterior que se imprimen los números 0 a 4 antes que se ejecute el callback con el texto Hola Nube Colectiva !. Esto se debe a la administración asíncrona de JavaScript de la cual hemos hablado anteriormente. El callback que registra el contenido del archivo llamado archivo.txt, solo se llamará después de leer el archivo.
Los callbacks también se pueden utilizar en métodos síncronos, por ejemplo Array.sort() acepta una función callback que permite personalizar como se deben ordenan los elementos.
Con esto tenemos una mejor idea de los callbacks y ahora veamos que son las Promises.
Que son las Promises ?
Un Promise traducido al español significa Promesa y en JavaScript tal como su nombre lo indica es una promesa de que un objeto eventualmente devolverá un valor o un error. Una Promesa (Promise) tiene 3 estados:
- Pendiente (Pending): El estado inicial que indica que la operación asíncrona no está completa.
- Cumplido (Fulfilled): Significa que la operación asíncrona se completo con éxito.
- Rechazado (Rejected): Significa que la operación asíncrona falló.
La mayoría de Promises terminan luciendo así:
1 2 3 4 5 6 7 8 9 10 11 |
miFuncionAsincrona() .then(data => { // Después de cumplir la promesa console.log(data); }) .catch(err => { // Si la promesa es rechazada console.error(err); }); |
Las Promesas (Promises) son importantes en JavaScript moderno, ya que se usan con las keywords async/await que se introdujeron en ECMAScript 2016.
Con async/await no es necesario utilizar callbacks o los métodos then() y catch() para escribir código asíncrono, por ejemplo si usamos await, se vería así:
1 2 3 4 5 6 7 8 |
try { const data = await miFuncionAsincrona(); } catch(err) { // Si la promesa es rechazada console.error(err); } |
El código anterior se parece mucho a un código JavaScript síncrono regular. Las librerías más populares de JavaScript y los nuevos proyectos usan Promises con las keywords async/await.
Sin embargo si estas actualizando un repositorio existente o te encuentras con una base de código heredada, probablemente le interese mover las APIs basadas en devolución de llamada a las APIs basadas en Promises para mejorar la experiencia de Desarrollo.
Convertir un Callback a Promise en Node JS
La mayoría de las funciones asíncronas que aceptan un callback en Node JS como el módulo fs (fyle system), tienen un estilo estándar de implementación: el callback se pasa como el último parámetro.
Por ejemplo, a continuación voy a leer un archivo usando fs.readFile() sin usar codificación de texto:
1 2 3 4 5 6 7 8 9 10 11 |
fs.readFile('./archivo.txt', (err, data) => { if (err) { console.error(err); return; } // Salida en consola (Datos en Buffer) console.log(data); }); |
Si ejecuto el codigo anterior ejecutando el comando node index obtengo:
1 2 3 4 |
// Obtengo <Buffer 48 6f 6c 61 20 4e 75 62 65 20 43 6f 6c 65 63 74 69 76 61 20 21> |
Y si especifico codificación de texto utf-8:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
const fs = require('fs'); fs.readFile('./archivo.txt', 'utf-8', (err, data) => { if (err) { // Manejo de errores console.error(err); return; } // Salida en consola console.log(data); }); |
Ejecuto el comando node index y obtengo como salida el String Hola Nube Colectiva !
1 2 3 4 5 |
node index Hola Nube Colectiva ! |
También el callback que se pasa a la función debe aceptar un Error como primer parámetro, después de eso, puede haber cualquier número de salidas.
Si la función que convierte a Promise sigue esas reglas, puede usar util.promisify, el cual es un módulo nativo de Node JS que convierte los Callbacks a Promises, para hacer eso de este módulo en el siguiente ejemplo, primero llamamos al módulo util, luego al módulo fs y por último al método promisify que convertirá fs.readFile a Promise:
1 2 3 4 5 |
const util = require('util'); const fs = require('fs'); const leerArchivo = util.promisify(fs.readFile); |
Luego usamos la función creada como una Promise regular:
1 2 3 4 5 6 7 8 9 |
readFile('./archivo.txt', 'utf-8') .then(data => { console.log(data); }) .catch(err => { console.log(err); }); |
Como alternativa podemos usar las keywords async/await como en el siguiente ejemplo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
const fs = require('fs'); const util = require('util'); const leerArchivo = util.promisify(fs.readFile); (async () => { try { const contenido = await leerArchivo('./archivo.txt', 'utf-8'); console.log(contenido); } catch (err) { console.error(err); } })(); |
Solo se puede usar la keyword await dentro de una función creada como async, de allí tenemos un contenedor (contenido) de funciones en el ejemplo, este contenedor de funciones se conoce como Expresiones de funciones invocadas inmediatamente.
Si el Callback no sigue ese estándar en particular, no hay de que preocuparnos, la función util.promisify() permite personalizar como ocurrirá la conversión.
Si tienes que trabajar con archivos mediante Promises, puedes utilizar la fs Promises API de Node JS.
Bien hasta ahora hemos aprendido como convertir los Callbacks a Promises de manera estándar haciendo uso del módulo util.promisify el cual solo esta disponible desde Node JS 8 o superior y si estas trabajando con una versión anterior a Node JS 8, lo mejor es crear una propia función basada en Promises y esto lo veremos a continuación.
Creando una Promise (Promesa) para versiones inferiores a Node JS 8
La idea es crear un nuevo objeto Promise que envuelva la función callback y si la función callback devuelve un error, rechazamos la Promise con el error. Si la función callback devuelve una salida sin error, resolvermos la Promise con la salida.
Empecemos convirtiendo un callback en Promise para una función que acepta un número fijo de parámetros:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
const fs = require('fs'); const leerArchivo = (nombreArchivo, codificacion) => { return new Promise((resolve, reject) => { fs.readFile(nombreArchivo, codificacion, (err, data) => { if (err) { return reject(err); } resolve(data); }); }); } leerArchivo('./archivo.txt') .then(data => { console.log(data); }) .catch(err => { console.log(err); }); |
La función leerArchivo() acepta los dos argumentos que he usado para leer archivos fs.readFile(). Entonces se crea un nuevo objeto Promise que envuelve la función que acepta el callback en este caso fs.readFile().
En ves de devolver un error, hago reject a la Promise. En lugar de registrar los datos de inmediato, hago resolve a la Promise. Luego uso mi función leerArchivo() basada en Promise.
Ahora probemos con otra función que acepta un número dinámico de parámetros:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
const obtenerValorMaximo = (callback, ...args) => { let maximo = -Infinity; for (let i of args) { if (i > maximo) { maximo = i; } } callback(maximo); } obtenerValorMaximo((maximo) => { console.log('El valor máximo es ' + maximo) }, 10, 2, 23, 1, 111, 20); // Obtengo El valor máximo es 111 |
El parámetro del callback es también el primer parámetro, lo que lo hace un poco inusual con funciones que aceptan callbacks.
La conversión a una Promise se realiza de la misma manera, creamos un nuevo objeto Promise que envuelve nuestra función que utiliza un callback. Entonces devuelve reject si se encuentra un error y resolve cuando tenemos la salida o resultado.
Entonces la versión de mi Promise (Promesa) se ve así:
1 2 3 4 5 6 7 8 9 10 11 12 |
const obtenerValorMaximoPromesa = (...args) => { return new Promise((resolve) => { obtenerValorMaximo((maximo) => { resolve(maximo); }, ...args); }); } obtenerValorMaximo(10, 2, 23, 1, 111, 20) .then(maximo => console.log(maximo)); |
Al crear nuestra Promise (Promesa), no importa si la función usa callbacks de una manera no estándar o con muchos argumentos. Tenemos control total de cómo se hace y los principios son los mismos.
Conclusión
Si bien los callbacks han sido la forma predeterminada de aprovechar el código asincrono en JavaScript, las Promises son un método más moderno que los Desarrolladores creen que es más fácil de usar, si alguna vez encontramos una base de código que usa callbacks, ahora podemos hacer que esa función sea una Promise.
Nota(s)
- Los ejemplos de códigos presentados en este Post pueden cambiar, ser modificados o continuar en futuras versiones de Node JS, esto no depende de mi, si no de los Desarrolladores que dan soporte a Node JS.
- No olvides que debemos usar la Tecnología para hacer cosas Buenas por el Mundo.
Síguenos en nuestras Redes Sociales para que no te pierdas nuestros próximos contenidos.