Diferencia entre async / await y rendimiento ES6 con generadores


82

Estaba leyendo este fantástico artículo « Generadores » y destaca claramente esta función, que es una función auxiliar para manejar las funciones del generador:

function async(makeGenerator){
  return function () {
    var generator = makeGenerator.apply(this, arguments);

    function handle(result){
      // result => { done: [Boolean], value: [Object] }
      if (result.done) return Promise.resolve(result.value);

      return Promise.resolve(result.value).then(function (res){
        return handle(generator.next(res));
      }, function (err){
        return handle(generator.throw(err));
      });
    }

    try {
      return handle(generator.next());
    } catch (ex) {
      return Promise.reject(ex);
    }
  }
}

que supongo que es más o menos la forma en que asyncse implementa la palabra clave con async/ await. Entonces, la pregunta es, si ese es el caso, ¿cuál es la diferencia entre la awaitpalabra clave y la yieldpalabra clave? ¿ awaitSiempre convierte algo en una promesa, mientras yieldque no ofrece tal garantía? ¡Esa es mi mejor suposición!

También puede ver cómo async/ awaites similar ayield generadores en este artículo donde describe la función 'spawn' de las funciones asíncronas de ES7 .


1
función asíncrona -> una corrutina. generador -> iterador que usa una corrutina para administrar su mecanismo de iteraciones internas. await suspende una corrutina, mientras que el rendimiento devuelve un resultado de una corrutina que usa algún generador
David Haim

1
async/awaitno es parte de ES7. Lea la descripción de la etiqueta.
Felix Kling

@david haim, sí, pero async await está construido sobre generadores, por lo que no son distintos
Alexander Mills

Respuestas:


46

yieldpuede considerarse el componente básico de await. yieldtoma el valor que se le da y lo pasa a la persona que llama. La persona que llama puede hacer lo que desee con ese valor (1). Más tarde, la persona que llama puede devolver un valor al generador (vía generator.next()) que se convierte en el resultado de la yieldexpresión (2), o un error que parecerá arrojado por la yieldexpresión (3).

async- awaitse puede considerar su uso yield. En (1) la persona que llama (es decir, el async- awaitpiloto - similar a la función informados) envolverá el valor de una promesa usando un algoritmo similar a new Promise(r => r(value)(nota, no Promise.resolve , pero eso no es un gran problema). Luego espera a que se resuelva la promesa. Si cumple, devuelve el valor cumplido a (2). Si rechaza, arroja el motivo del rechazo como un error en (3).

Entonces, la utilidad de async- awaites esta maquinaria que usa yieldpara desenvolver el valor entregado como una promesa y devolver su valor resuelto, repitiendo hasta que la función devuelve su valor final.


verifique esta respuesta stackoverflow.com/a/39384160/3933557 que contradice este argumento. async-await parece similar a yield pero usa una cadena de promesas bajo el capó. Por favor, comparta si tiene algún buen recurso que diga "se puede considerar que async-await use yield".
Samarendra

No estoy seguro de cómo está tomando esa respuesta para "contradecir este argumento", porque dice lo mismo que esta respuesta. > Mientras tanto, los transpilers como Babel te permiten escribir async / await y convertir el código en generadores.
Arnavion

dice que babel se convierte en generadores, pero lo que está diciendo es que "el rendimiento puede considerarse el componente básico de await" y "async-await puede considerarse que usa rendimiento". lo cual no es correcto a mi entender (sujeto a corrección). async-await utiliza internamente cadenas de promesa como se menciona en esa respuesta. Quiero entender si hay algo que me falta, ¿puede compartir sus pensamientos al respecto?
Samarendra

Esta respuesta no afirma que todos los motores ES de todo el mundo implementen internamente las promesas mediante el uso de generadores. Algunos pueden; algunos pueden no hacerlo; es irrelevante para la pregunta a la que esta es una respuesta. Sin embargo, la forma en que funcionan las promesas se puede entender utilizando generadores con una forma particular de accionar el generador, y eso es lo que explica esta respuesta.
Arnavion

43

Bueno, resulta que hay una relación muy estrecha entre async/ awaity generadores. Y creo async/ awaitsiempre serán construidos en los generadores. Si miras la forma en que Babel transpila async/ await:

Babel toma esto:

this.it('is a test', async function () {

    const foo = await 3;
    const bar = await new Promise(resolve => resolve('7'));
    const baz = bar * foo;
    console.log(baz);

});

y lo convierte en esto

function _asyncToGenerator(fn) {
    return function () {
        var gen = fn.apply(this, arguments);
        return new Promise(function (resolve, reject) {
            function step(key, arg) {
                try {
                    var info = gen[key](arg);
                    var value = info.value;
                } catch (error) {
                    reject(error);
                    return;
                }
                if (info.done) {
                    resolve(value);
                } else {
                    return Promise.resolve(value).then(function (value) {
                        return step("next", value);
                    }, function (err) {
                        return step("throw", err);
                    });
                }
            }

            return step("next");
        });
    };
}


this.it('is a test', _asyncToGenerator(function* () {   // << now it's a generator

    const foo = yield 3;    //  <<< now it's yield, not await
    const bar = yield new Promise(resolve => resolve(7));
    const baz = bar * foo;
    console.log(baz);

}));

haces las matemáticas.

Esto hace que parezca que la asyncpalabra clave es solo esa función contenedora, pero si ese es el caso, awaitsimplemente se convierte enyield , probablemente habrá un poco más en la imagen más adelante cuando se conviertan en nativas.

Puede ver más explicaciones para esto aquí: https://www.promisejs.org/generators/


1
NodeJS tiene async nativo / espera por un tiempo, sin generadores: codeforgeek.com/2017/02/…
Bram

3
La implementación nativa de @Bram absolutamente usa generadores bajo el capó, lo mismo, simplemente abstraído.
Alexander Mills

3
No lo creo. Async / await se implementa de forma nativa en el motor V8. Generadores donde una característica de ES6, async / await es ES7. Fue parte de la versión 5.5 del motor V8 (que se usa en Node): v8project.blogspot.nl/2016/10/v8-release-55.html . Es posible transpilar ES7 async / await en generadores ES6, pero con las nuevas versiones de NodeJS esto ya no es necesario, y el rendimiento de async / await incluso parece ser mejor que los generadores: medium.com/@markherhold/…
Bram

1
async / await usa generadores para hacer lo suyo
Alexander Mills

@AlexanderMills, ¿puede compartir algunos recursos legítimos que dicen que async / await usa generadores internamente? verifique esto ans stackoverflow.com/a/39384160/3933557 que contradice este argumento. Creo que el hecho de que Babel use generadores no significa que se implemente de manera similar bajo el capó. Alguna idea sobre esto
Samarendra

28

¿Cuál es la diferencia entre la awaitpalabra clave y la yieldpalabra clave?

La awaitpalabra clave solo debe usarse en async functions, mientras que la yieldpalabra clave solo debe usarse en generadores function*. Y esos también son obviamente diferentes: uno devuelve promesas, el otro devuelve generadores.

¿ awaitSiempre convierte algo en una promesa, mientras yieldque no ofrece tal garantía?

Sí, awaitllamará Promise.resolveal valor esperado.

yield simplemente produce el valor fuera del generador.


Un detalle menor, pero como mencioné en mi respuesta, la especificación no usa Promise.resolve (solía hacerlo antes), usa PromiseCapability :: resolve, que está representado con mayor precisión por el constructor Promise.
Arnavion

@Arnavion: Promise.resolveusa exactamente lo mismo new PromiseCapability(%Promise%)que la especificación async / await usa directamente, solo pensé que Promise.resolvees mejor entenderlo.
Bergi

1
Promise.resolvetiene un cortocircuito extra "IsPromise == true? then return same value" que async no tiene. Es decir, await pdonde phay una promesa devolverá una nueva promesa que resuelve p, mientras Promise.resolve(p)que volvería p.
Arnavion

Oh, me perdí eso, pensé que esto solo Promise.castestaba disponible y estaba en desuso por razones de coherencia. Pero no importa, realmente no vemos esa promesa de todos modos.
Bergi

2
var r = await p; console.log(r);debería transformarse en algo como:, p.then(console.log);mientras que ppodría crearse como:, var p = new Promise(resolve => setTimeout(resolve, 1000, 42));por lo que es incorrecto decir " await llama a Promise.resolve", es algún otro código totalmente alejado de la expresión 'await' que invoca Promise.resolve, por lo que la awaitexpresión transformada , es decir Promise.then(console.log), se invoca y se imprime 42.
Dejavu

14

tl; dr

Uso async/ await99% del tiempo sobre generadores. ¿Por qué?

  1. async/ awaitreemplaza directamente el flujo de trabajo más común de las cadenas de promesas permitiendo que el código se declare como si fuera sincrónico, simplificándolo drásticamente.

  2. Los generadores abstraen el caso de uso en el que llamarías a una serie de operaciones asíncronas que dependen unas de otras y que eventualmente estarán en un estado "terminado". El ejemplo más simple sería buscar resultados que eventualmente devuelvan el último conjunto, pero solo llamaría a una página según sea necesario, no inmediatamente en sucesión.

  3. async/ awaites en realidad una abstracción construida sobre generadores para facilitar el trabajo con promesas.

Vea una explicación muy detallada de Async / Await vs.Generadores


4

Pruebe estos programas de prueba que solía entender await/ asynccon promesas.

Programa # 1: sin promesas no se ejecuta en secuencia

function functionA() {
    console.log('functionA called');
    setTimeout(function() {
        console.log('functionA timeout called');
        return 10;
    }, 15000);

}

function functionB(valueA) {
    console.log('functionB called');
    setTimeout(function() {
        console.log('functionB timeout called = ' + valueA);
        return 20 + valueA;
    }, 10000);
}

function functionC(valueA, valueB) {

    console.log('functionC called');
    setTimeout(function() {
        console.log('functionC timeout called = ' + valueA);
        return valueA + valueB;
    }, 10000);

}

async function executeAsyncTask() {
    const valueA = await functionA();
    const valueB = await functionB(valueA);
    return functionC(valueA, valueB);
}
console.log('program started');
executeAsyncTask().then(function(response) {
    console.log('response called = ' + response);
});
console.log('program ended');

Programa # 2: con promesas

function functionA() {
    return new Promise((resolve, reject) => {
        console.log('functionA called');
        setTimeout(function() {
            console.log('functionA timeout called');
            // return 10;
            return resolve(10);
        }, 15000);
    });   
}

function functionB(valueA) {
    return new Promise((resolve, reject) => {
        console.log('functionB called');
        setTimeout(function() {
            console.log('functionB timeout called = ' + valueA);
            return resolve(20 + valueA);
        }, 10000);

    });
}

function functionC(valueA, valueB) {
    return new Promise((resolve, reject) => {
        console.log('functionC called');
        setTimeout(function() {
            console.log('functionC timeout called = ' + valueA);
            return resolve(valueA + valueB);
        }, 10000);

    });
}

async function executeAsyncTask() {
    const valueA = await functionA();
    const valueB = await functionB(valueA);
    return functionC(valueA, valueB);
}
console.log('program started');
executeAsyncTask().then(function(response) {
    console.log('response called = ' + response);
});
console.log('program ended');

0

En muchos sentidos, los generadores son un superconjunto de async / await. En este momento, async / await tiene rastros de pila más limpios que co , la biblioteca basada en generador async / await más popular. Puede implementar su propia versión de async / await usando generadores y agregar nuevas características, como soporte integrado para yieldno promesas o compilarlo en observables RxJS.

Entonces, en resumen, los generadores le brindan más flexibilidad y las bibliotecas basadas en generadores generalmente tienen más funciones. Pero async / await es una parte central del lenguaje, está estandarizado y no cambiará debajo de usted, y no necesita una biblioteca para usarlo. Tengo una publicación de blog con más detalles sobre la diferencia entre async / await y generators.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.