Node.js Async Await en ES7

    Una de las características más interesantes de JavaScript (y por tanto de Node.js) es la async/await sintaxis introducida en ES7. Aunque básicamente es solo azúcar sintáctico además de Promises, estas dos palabras clave por sí solas deberían hacer que escribir código asincrónico en Node sea mucho más llevadero. Todo, pero elimina el problema del infierno de devolución de llamada, e incluso nos permite usar estructuras de flujo de control alrededor de nuestro código asincrónico.

    A lo largo de este artículo, veremos qué está mal con Promesas, cómo las nuevas await característica puede ayudar, y cómo puede comenzar a usarla ahora mismo.

    El problema de las promesas

    El concepto de una «promesa» en JavaScript ha existido por un tiempo, y se ha utilizado durante años gracias a bibliotecas de terceros como Azulejo y q, sin mencionar el soporte nativo agregado recientemente en ES6.

    Han sido una gran solución al problema del infierno de devolución de llamada, pero desafortunadamente no resuelven todos los problemas asincrónicos. Si bien es una gran mejora, Promises nos deja con ganas de una simplificación aún mayor.

    Digamos que desea usar la API REST de Github para encontrar la cantidad de estrellas que tiene un proyecto. En este caso, probablemente usaría la gran solicitud-promesa biblioteca. Con el enfoque basado en promesas, debe realizar la solicitud y obtener el resultado dentro de la devolución de llamada a la que pasa .then(), Me gusta esto:

    var request = require('request-promise');
    
    var options = {
        url: 'https://api.github.com/repos/scottwrobinson/camo',
        headers: {
            'User-Agent': 'YOUR-GITHUB-USERNAME'
        }
    };
    
    request.get(options).then(function(body) {
        var json = JSON.parse(body);
        console.log('Camo has', json.stargazers_count, 'stars!');
    });
    

    Esto imprimirá algo como:

    $ node index.js
    Camo has 1,000,000 stars!
    

    De acuerdo, tal vez ese número sea una ligera exageración, pero entiendes el punto;)

    Hacer solo una solicitud como esta no es demasiado difícil con Promises, pero ¿qué pasa si queremos hacer la misma solicitud para muchos repositorios diferentes en GitHub? ¿Y qué sucede si necesitamos agregar un flujo de control (como condicionales o bucles) alrededor de las solicitudes? A medida que sus requisitos se vuelven más complicados, es más difícil trabajar con Promesas y aún así terminan complicando su código. Siguen siendo mejores que las devoluciones de llamada normales ya que no tiene un anidamiento ilimitado, pero no resuelven todos sus problemas.

    Para escenarios más complicados como el del siguiente código, necesita aprender a encadenar promesas y comprender cuándo y dónde se ejecuta su código asincrónico.

    "use strict";
    
    var request = require('request-promise');
    
    var headers = {
        'User-Agent': 'YOUR-GITHUB-USERNAME'
    };
    
    var repos = [
        'scottwrobinson/camo',
        'facebook/react',
        'scottwrobinson/twentyjs',
        'moment/moment',
        'nodejs/node',
        'lodash/lodash'
    ];
    
    var issueTitles = [];
    
    var reqs = Promise.resolve();
    
    repos.forEach(function(r) {
        var options = { url: 'https://api.github.com/repos/' + r, headers: headers };
    
        reqs = reqs.then(function() {
            return request.get(options);
        }).then(function(body) {
            var json = JSON.parse(body);
    
            var p = Promise.resolve();
    
            // Only make request if it has open issues
            if (json.has_issues) {
                var issuesOptions = { url: 'https://api.github.com/repos/' + r + '/issues', headers: headers };
                p = request.get(issuesOptions).then(function(ibody) {
                    var issuesJson = JSON.parse(ibody);
    
                    if (issuesJson[0]) {
                        issueTitles.push(issuesJson[0].title);
                    }
                });
            }
    
            return p;
        });
    });
    
    reqs.then(function() {
        console.log('Issue titles:');
        issueTitles.forEach(function
            console.log
        });
    });
    

    Nota: Github limita agresivamente las solicitudes no autenticadas, así que no se sorprenda si se corta después de ejecutar el código anterior solo unas pocas veces. Puede aumentar este límite pasar una identificación / secreto de cliente.

    En el momento de escribir este artículo, la ejecución de este código produciría lo siguiente:

    $ node index.js
    Issue titles:
    feature request: bulk create/save support
    Made renderIntoDocument tests asynchronous.
    moment issue template
    test: robust handling of env for npm-test-install
    

    Solo agregando un for bucle y un if declaración a nuestro código asincrónico hace que sea mucho más difícil de leer y comprender. Este tipo de complejidad solo se puede mantener durante un tiempo antes de que sea demasiado difícil trabajar con él.

    Mirando el código, ¿puede decirme inmediatamente dónde se ejecutan realmente las solicitudes o en qué orden se ejecuta cada bloque de código? Probablemente no sin leerlo detenidamente.

    Simplificando con Async / Await

    El nuevo async/await la sintaxis le permite seguir usando Promises, pero elimina la necesidad de proporcionar una devolución de llamada al encadenado then() métodos. El valor que se hubiera enviado al then() En cambio, la devolución de llamada se devuelve directamente desde la función asincrónica, como si fuera una función de bloqueo sincrónica.

    let value = await myPromisifiedFunction();
    

    Aunque aparentemente simple, esta es una gran simplificación del diseño de código JavaScript asincrónico. La única sintaxis adicional necesaria para lograr esto es la await palabra clave. Por lo tanto, si comprende cómo funcionan las promesas, no será demasiado difícil comprender cómo usar estas nuevas palabras clave, ya que se basan en el concepto de promesas. Todo lo que tienes que saber es que cualquier promesa puede ser await-ed. Los valores también pueden ser await-ed, al igual que una Promise puede .resolve() en un número entero o una cadena.

    Comparemos el método basado en promesas con el await palabra clave:

    Promesas

    var request = require('request-promise');
    
    request.get('https://api.github.com/repos/scottwrobinson/camo').then(function(body) {
        console.log('Body:', body);
    });
    

    esperar

    var request = require('request-promise');
    
    async function main() {
        var body = await request.get('https://api.github.com/repos/scottwrobinson/camo');
        console.log('Body:', body);
    }
    main();
    

    Como se puede ver, await indica que desea resolver la Promesa y no devolver ese objeto Promesa real como lo haría normalmente. Cuando se ejecuta esta línea, el request La llamada se colocará en la pila del bucle de eventos y la ejecución cederá a otro código asincrónico que está listo para ser procesado.

    los async La palabra clave se usa cuando está definiendo una función que contiene código asincrónico. Este es un indicador de que la función devuelve una Promesa y, por lo tanto, debe tratarse como asincrónica.

    Aquí hay un ejemplo simple de su uso (observe el cambio en la definición de la función):

    async function getCamoJson() {
        var options = {
            url: 'https://api.github.com/repos/scottwrobinson/camo',
            headers: {
                'User-Agent': 'YOUR-GITHUB-USERNAME'
            }
        };
        return await request.get(options);
    }
    
    var body = await getCamoJson();
    

    Ahora que sabemos cómo usar async y await juntos, veamos cómo se ve ahora el código más complejo basado en Promesas de antes:

    "use strict";
    
    var request = require('request-promise');
    
    var headers = {
        'User-Agent': 'scottwrobinson'
    };
    
    var repos = [
        'scottwrobinson/camo',
        'facebook/react',
        'scottwrobinson/twentyjs',
        'moment/moment',
        'nodejs/node',
        'lodash/lodash'
    ];
    
    var issueTitles = [];
    
    async function main() {
        for (let i = 0; i < repos.length; i++) {
            let options = { url: 'https://api.github.com/repos/' + repos[i], headers: headers };
            let body = await request.get(options);
            let json = JSON.parse(body);
    
            if (json.has_issues) {
                let issuesOptions = { url: 'https://api.github.com/repos/' + repos[i] + '/issues', headers: headers };
                let ibody = await request.get(issuesOptions);
                let issuesJson = JSON.parse(ibody);
    
                if (issuesJson[0]) {
                    issueTitles.push(issuesJson[0].title);
                }
            }
        }
    
        console.log('Issue titles:');
        issueTitles.forEach(function
            console.log
        });
    }
    
    main();
    

    Sin duda, es más legible ahora que se puede escribir como muchos otros lenguajes ejecutados linealmente.

    Ahora el único problema es que cada request.get() la llamada se ejecuta en serie (lo que significa que cada llamada tiene que esperar hasta que la llamada anterior haya terminado antes de ejecutarse), por lo que tenemos que esperar más para que el código complete la ejecución antes de obtener nuestros resultados. La mejor opción sería ejecutar las solicitudes HTTP GET en paralelo. Esto todavía se puede hacer utilizando Promise.all() como lo hubiéramos hecho antes. Simplemente reemplace el for bucle con un .map() llamar y enviar la matriz resultante de promesas a Promise.all(), Me gusta esto:

    // Init code omitted...
    
    async function main() {
        let reqs = repos.map(async function(r) {
            let options = { url: 'https://api.github.com/repos/' + r, headers: headers };
            let body = await request.get(options);
            let json = JSON.parse(body);
    
            if (json.has_issues) {
                let issuesOptions = { url: 'https://api.github.com/repos/' + r + '/issues', headers: headers };
                let ibody = await request.get(issuesOptions);
                let issuesJson = JSON.parse(ibody);
    
                if (issuesJson[0]) {
                    issueTitles.push(issuesJson[0].title);
                }
            }
        });
    
        await Promise.all(reqs);
    }
    
    main();
    

    De esta forma puede aprovechar la velocidad de ejecución paralela y la simplicidad de await.

    Hay más beneficios que solo poder usar el flujo de control tradicional como bucles y condicionales. Este enfoque lineal nos permite volver a utilizar el try...catch declaración para el manejo de errores. Con Promises tenías que usar el .catch() método, que funcionó, pero podría causar confusión al determinar para qué promesas capturó excepciones.

    Así que ahora esto …

    request.get('https://api.github.com/repos/scottwrobinson/camo').then(function(body) {
        console.log(body);
    }).catch(function(err) {
        console.log('Got an error:', err.message);
    });
    
    // Got an error: 403 - "Request forbidden by administrative rules. Please make sure your request has a User-Agent header..."
    

    … se puede expresar así:

    try {
        var body = await request.get('https://api.github.com/repos/scottwrobinson/camo');
        console.log(body);
    } catch(err) {
        console.log('Got an error:', err.message)
    }
    
    // Got an error: 403 - "Request forbidden by administrative rules. Please make sure your request has a User-Agent header..."
    

    Si bien se trata de la misma cantidad de código, es mucho más fácil de leer y comprender para alguien que realiza la transición a JavaScript desde otro idioma.

    Usando Async ahora mismo

    La función asincrónica todavía se encuentra en la etapa de propuesta, pero no se preocupe, todavía hay algunas formas en que puede usar esto en su código ahora mismo.

    V8

    Si bien aún no ha llegado a Node, el equipo V8 ha declarado públicamente su intención de implementar el async/await característica. Incluso ya han comprometido la implementación del tiempo de ejecución del prototipo, lo que significa que el soporte de armonía no debería quedarse atrás.

    Babel

    Podría decirse que la opción más popular es transpilar su código usando Babel y sus diversos plugins. Babel es extremadamente popular gracias a su capacidad para mezclar y combinar las funciones de ES6 y ES7 utilizando su sistema de plugins. Aunque es un poco más complicado de configurar, también proporciona mucho más control al desarrollador.

    Regenerador

    los regenerador El proyecto de Facebook no tiene tantas funciones como Babel, pero es una forma más sencilla de hacer funcionar la transpilación asíncrona.

    El mayor problema que he tenido con él es que sus errores no son muy descriptivos. Entonces, si hay un error de sintaxis en su código, no obtendrá mucha ayuda del regenerador para encontrarlo. Aparte de eso, he sido feliz con eso.

    Traceur

    No tengo ninguna experiencia con este personalmente, pero Traceur (de Google) parece ser otra opción popular con muchas funciones disponibles. Puedes encontrar más información aquí para obtener detalles sobre las características de ES6 y ES7 que se pueden transpilar.

    asyncawait

    La mayoría de las opciones disponibles para usted implican transpilar o usar una compilación nocturna de V8 para obtener async trabajando. Otra opción es utilizar el asyncawait paquete, que proporciona una función para resolver Promesas de manera similar a la await característica. Es una buena forma de vainilla ES5 de obtener una sintaxis similar.

    Conclusión

    ¡Y eso es! Personalmente, estoy muy emocionado con esta función en ES7, pero hay otras excelentes funciones en ES7 que deberías comprobar, como los decoradores de clases y las propiedades.

    ¿Utiliza código ES7 transpilado? Si es así, ¿qué característica ha sido la más beneficiosa para su trabajo? ¡Háznoslo saber en los comentarios!

     

    Deja una respuesta

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