Conversi贸n de devoluciones de llamada en promesas en Node.js

    Introducci贸n

    Hace unos a帽os, las devoluciones de llamada eran la 煤nica forma en que pod铆amos lograr la ejecuci贸n de c贸digo asincr贸nico en JavaScript. Hubo pocos problemas con las devoluciones de llamada y el m谩s notable fue el “infierno de las devoluciones de llamada”.

    Con ES6, Promises se introdujo como una soluci贸n a esos problemas. Y finalmente, el async/await Se introdujeron palabras clave para una experiencia a煤n m谩s agradable y una mejor legibilidad.

    Incluso con la adici贸n de nuevos enfoques, todav铆a hay muchos m贸dulos nativos y bibliotecas que usan devoluciones de llamada. En este art铆culo, hablaremos sobre c贸mo convertir las devoluciones de llamada de JavaScript en Promesas. El conocimiento de ES6 ser谩 煤til, ya que usaremos funciones como operadores de propagaci贸n para facilitar las cosas.

    驴Qu茅 es una devoluci贸n de llamada?

    Una devoluci贸n de llamada es un argumento de 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 asincr贸nicas.

    JavaScript es un lenguaje interpretado que solo puede procesar una l铆nea de c贸digo a la vez. Algunas tareas pueden tardar bastante en completarse, como descargar o leer un archivo grande. JavaScript descarga estas tareas de larga ejecuci贸n a un proceso diferente en el navegador o el entorno de Node.js. De esa manera, no bloquea la ejecuci贸n del resto del c贸digo.

    Por lo general, las funciones asincr贸nicas aceptan una funci贸n de devoluci贸n de llamada, de modo que cuando est茅n completas podamos procesar sus datos.

    Tomemos un ejemplo, escribiremos una funci贸n de devoluci贸n de llamada que se ejecutar谩 cuando el programa lea con 茅xito un archivo de nuestro disco duro.

    Con este fin, usaremos un archivo de texto llamado sample.txt, que contiene lo siguiente:

    Hello world from sample.txt
    

    Luego, escriba un script simple de Node.js para leer el archivo:

    const fs = require('fs');
    
    fs.readFile('./sample.txt', 'utf-8', (err, data) => {
        if (err) {
            // Handle error
            console.error(err);
              return;
        }
    
        // Data is string do something with it
        console.log(data);
    });
    
    for (let i = 0; i < 10; i++) {
        console.log(i);
    }
    

    Ejecutar este c贸digo deber铆a producir:

    0
    ...
    8
    9
    Hello world from sample.txt
    

    Si ejecuta este c贸digo, deber铆a ver 0..9 que se imprime antes de que se ejecute la devoluci贸n de llamada. Esto se debe a la gesti贸n asincr贸nica de JavaScript de la que hemos hablado anteriormente. La devoluci贸n de llamada, que registra el contenido del archivo, solo se llamar谩 despu茅s de que se lea el archivo.

    Como nota al margen, las devoluciones de llamada tambi茅n se pueden usar en m茅todos s铆ncronos. Por ejemplo, Array.sort() acepta una funci贸n de devoluci贸n de llamada que le permite personalizar c贸mo se ordenan los elementos.

    Las funciones que aceptan devoluciones de llamada se denominan funciones de orden superior.

    Ahora tenemos una mejor idea de las devoluciones de llamada. Sigamos adelante y veamos qu茅 es una Promesa.

    驴Qu茅 es una promesa?

    Se introdujeron promesas con ECMAScript 2015 (com煤nmente conocido como ES6) para mejorar la experiencia del desarrollador con la programaci贸n asincr贸nica. Como sugiere su nombre, es una promesa de que un objeto JavaScript eventualmente devolver谩 un valor o un error.

    Una promesa tiene 3 estados:

    • Pendiente: El estado inicial que indica que la operaci贸n asincr贸nica no est谩 completa.
    • Cumplido: Lo que significa que la operaci贸n asincr贸nica se complet贸 correctamente.
    • Rechazado: Significa que la operaci贸n asincr贸nica fall贸.

    La mayor铆a de las promesas terminan luciendo as铆:

    someAsynchronousFunction()
        .then(data => {
            // After promise is fulfilled
            console.log(data);
        })
        .catch(err => {
            // If promise is rejected
            console.error(err);
        });
    

    Las promesas son importantes en JavaScript moderno, ya que se utilizan con async/await palabras clave que se introdujeron en ECMAScript 2016. Con async/await, no necesitamos utilizar devoluciones de llamada ni then() y catch() para escribir c贸digo asincr贸nico.

    Si se adaptara el ejemplo anterior, se ver铆a as铆:

    try {
        const data = await someAsynchronousFunction();
    } catch(err) {
        // If promise is rejected
        console.error(err);
    }
    

    隆Esto se parece mucho a JavaScript s铆ncrono “normal”! Puedes aprender m谩s sobre async/await en nuestro art铆culo, Node.js Async Await en ES7.

    Las bibliotecas JavaScript m谩s populares y los proyectos nuevos usan Promises con la async/await palabras clave.

    Sin embargo, si est谩 actualizando un repositorio existente o encuentra una base de c贸digo heredada, probablemente le interese mover las API basadas en devoluci贸n de llamada a una API basada en Promise para mejorar su experiencia de desarrollo. Tu equipo tambi茅n estar谩 agradecido.

    隆Veamos un par de m茅todos para convertir devoluciones de llamada en promesas!

    Convertir una devoluci贸n de llamada en una promesa

    Node.js Promisify

    La mayor铆a de las funciones asincr贸nicas que aceptan una devoluci贸n de llamada en Node.js, como la fs (sistema de archivos), tiene un estilo de implementaci贸n est谩ndar: la devoluci贸n de llamada se pasa como 煤ltimo par谩metro.

    Por ejemplo, as铆 es como puede leer un archivo usando fs.readFile() sin especificar la codificaci贸n del texto:

    fs.readFile('./sample.txt', (err, data) => {
        if (err) {
            console.error(err);
              return;
        }
    
        // Data is a buffer
        console.log(data);
    });
    

    Nota: Si especifica utf-8 como la codificaci贸n obtendr谩 una salida de cadena. Si no especifica la codificaci贸n, obtendr谩 un Buffer salida.

    Adem谩s, la devoluci贸n de llamada, que se pasa a la funci贸n, debe aceptar una Error ya que es el primer par谩metro. Despu茅s de eso, puede haber cualquier n煤mero de salidas.

    Si la funci贸n que necesita convertir en una Promesa sigue esas reglas, puede usar util.promisify, un m贸dulo nativo de Node.js que oculta las devoluciones de llamada a Promises.

    Para hacer eso, primero importe el util m贸dulo:

    const util = require('util');
    

    Entonces usas el promisify m茅todo para convertirlo en una promesa:

    const fs = require('fs');
    const readFile = util.promisify(fs.readFile);
    

    Ahora use la funci贸n reci茅n creada como una promesa regular:

    readFile('./sample.txt', 'utf-8')
        .then(data => {
            console.log(data);
        })
        .catch(err => {
            console.log(err);
        });
    

    Alternativamente, puede utilizar el async/await palabras clave como se muestra en el siguiente ejemplo:

    const fs = require('fs');
    const util = require('util');
    
    const readFile = util.promisify(fs.readFile);
    
    (async () => {
        try {
            const content = await readFile('./sample.txt', 'utf-8');
            console.log(content);
        } catch (err) {
            console.error(err);
        }
    })();
    

    Solo puedes usar el await palabra clave dentro de una funci贸n que se cre贸 con async, de ah铆 la raz贸n por la que tenemos un contenedor de funciones en este ejemplo. Este contenedor de funci贸n tambi茅n se conoce como Expresiones de funci贸n invocadas inmediatamente.

    Si su devoluci贸n de llamada no sigue ese est谩ndar en particular, no se preocupe. los util.promisify() La funci贸n puede permitirle personalizar c贸mo ocurre la conversi贸n.

    Nota: Las promesas se hicieron populares poco despu茅s de su presentaci贸n. Node.js ya ha convertido la mayor铆a, si no todas, de sus funciones principales de una devoluci贸n de llamada a una API basada en Promise.

    Si necesita trabajar con archivos usando Promesas, use el biblioteca que viene con Node.js.

    Hasta ahora, ha aprendido c贸mo convertir las devoluciones de llamada de estilo est谩ndar de Node.js en promesas. Este m贸dulo solo est谩 disponible en Node.js a partir de la versi贸n 8. Si est谩 trabajando en el navegador o en una versi贸n anterior de Node, probablemente ser铆a mejor que creara su propia versi贸n de la funci贸n basada en promesas.

    Creando tu promesa

    Hablemos sobre c贸mo ocultar devoluciones de llamada a promesas si el util.promisify() La funci贸n no est谩 disponible.

    La idea es crear un nuevo Promise objeto que envuelve la funci贸n de devoluci贸n de llamada. Si la funci贸n de devoluci贸n de llamada devuelve un error, rechazamos la Promesa con el error. Si la funci贸n de devoluci贸n de llamada devuelve una salida sin errores, resolvemos la Promesa con la salida.

    Comencemos por convertir una devoluci贸n de llamada en una promesa para una funci贸n que acepta un n煤mero fijo de par谩metros:

    const fs = require('fs');
    
    const readFile = (fileName, encoding) => {
        return new Promise((resolve, reject) => {
            fs.readFile(fileName, encoding, (err, data) => {
                if (err) {
                    return reject(err);
                }
    
                resolve(data);
            });
        });
    }
    
    readFile('./sample.txt')
        .then(data => {
            console.log(data);
        })
        .catch(err => {
            console.log(err);
        });
    

    Nuestra nueva funci贸n readFile() acepta los dos argumentos que hemos estado usando para leer archivos con fs.readFile(). Luego creamos un nuevo Promise objeto que envuelve la funci贸n, que acepta la devoluci贸n de llamada, en este caso, fs.readFile().

    En lugar de devolver un error, reject la promesa. En lugar de registrar los datos de inmediato, resolve la promesa. Luego usamos nuestro basado en promesas readFile() funciona como antes.

    Probemos con otra funci贸n que acepte un n煤mero din谩mico de par谩metros:

    const getMaxCustom = (callback, ...args) => {
        let max = -Infinity;
    
        for (let i of args) {
            if (i > max) {
                max = i;
            }
        }
    
        callback(max);
    }
    
    getMaxCustom((max) => { console.log('Max is ' + max) }, 10, 2, 23, 1, 111, 20);
    

    El par谩metro de devoluci贸n de llamada tambi茅n es el primer par谩metro, lo que lo hace un poco inusual con funciones que aceptan devoluciones de llamada.

    La conversi贸n a una promesa se realiza de la misma manera. Creamos un nuevo Promise objeto que envuelve nuestra funci贸n que usa una devoluci贸n de llamada. Nosotros entonces reject si encontramos un error y resolve cuando tengamos el resultado.

    Nuestra versi贸n prometida se ve as铆:

    const getMaxPromise = (...args) => {
        return new Promise((resolve) => {
            getMaxCustom((max) => {
                resolve(max);
            }, ...args);
        });
    }
    
    getMaxCustom(10, 2, 23, 1, 111, 20)
        .then(max => console.log(max));
    

    Al crear nuestra promesa, no importa si la funci贸n usa devoluciones de llamada de una manera no est谩ndar o con muchos argumentos. Tenemos el control total de c贸mo se hace y los principios son los mismos.

    Conclusi贸n

    Si bien las devoluciones de llamada han sido la forma predeterminada de aprovechar el c贸digo asincr贸nico en JavaScript, las promesas 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 utiliza devoluciones de llamada, ahora podemos hacer que esa funci贸n sea una Promesa.

    En este art铆culo, vio por primera vez c贸mo usar utils.promisfy() en Node.js para convertir funciones que aceptan devoluciones de llamada en promesas. Luego viste c贸mo crear el tuyo propio Promise objeto que envuelve una funci贸n que acepta una devoluci贸n de llamada sin el uso de bibliotecas externas.

    Con esto, una gran cantidad de c贸digo JavaScript heredado se puede mezclar f谩cilmente con pr谩cticas y bases de c贸digo m谩s modernas. Como siempre, el c贸digo fuente est谩 disponible en GitHub.

    Etiquetas:

    Deja una respuesta

    Tu direcci贸n de correo electr贸nico no ser谩 publicada. Los campos obligatorios est谩n marcados con *