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 *