Unterschied zwischen Async / Warten und ES6-Ertrag bei Generatoren


82

Ich habe gerade diesen fantastischen Artikel « Generatoren » gelesen und er hebt diese Funktion deutlich hervor, die eine Hilfsfunktion für die Handhabung von Generatorfunktionen ist:

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);
    }
  }
}

Ich gehe davon aus, dass das asyncSchlüsselwort mehr oder weniger mit async/ implementiert wird await. Die Frage ist also, wenn dies der Fall ist, was zum Teufel ist dann der Unterschied zwischen dem awaitSchlüsselwort und dem yieldSchlüsselwort? Wird awaitaus etwas immer ein Versprechen, während yieldes keine solche Garantie gibt? Das ist meine beste Vermutung!

Sie können auch sehen, wie async/ mit Generatoren awaitähnlich ist, yieldin diesem Artikel, in dem er die asynchronen Funktionen der ' Spawn' -Funktion ES7 beschreibt .


1
asynchrone Funktion -> eine Coroutine. Generator -> Iterator, der eine Coroutine verwendet, um seinen inneren Iterationsmechanismus zu verwalten. Warten Sie, bis eine Coroutine suspendiert ist, während der Ertrag ein Ergebnis einer Coroutine zurückgibt, die einige Generatoren verwenden
David Haim,

1
async/awaitist nicht Teil von ES7. Bitte lesen Sie die Tag-Beschreibung.
Felix Kling

@ David Haim, ja, aber asynchrone Warten ist auf Generatoren aufgebaut, so dass sie nicht verschieden sind
Alexander Mills

Antworten:


46

yieldkann als Baustein von betrachtet werden await. yieldNimmt den angegebenen Wert und gibt ihn an den Anrufer weiter. Mit diesem Wert kann der Anrufer dann machen, was er will (1). Später kann der Aufrufer dem Generator (via generator.next()) einen Wert zurückgeben, der das Ergebnis des yieldAusdrucks (2) wird, oder einen Fehler, der vom yieldAusdruck (3) ausgelöst zu werden scheint .

async- awaitkann als verwendet werden yield. Bei (1) wird der Aufrufer (dh der async- awaitTreiber - ähnlich der von Ihnen geposteten Funktion) den Wert mit einem ähnlichen Algorithmus wie in ein Versprechen einschließen new Promise(r => r(value)(beachten Sie, nicht Promise.resolve , aber das ist keine große Sache). Es wartet dann auf die Lösung des Versprechens. Wenn es erfüllt, gibt es den erfüllten Wert bei (2) zurück. Wenn es ablehnt, wirft es den Ablehnungsgrund als Fehler bei (3).

Der Nutzen von async- awaitist also diese Maschinerie, mit yieldder der erbrachte Wert als Versprechen ausgepackt und der aufgelöste Wert zurückgegeben wird. Wiederholen Sie diesen Vorgang, bis die Funktion ihren endgültigen Wert zurückgibt.


Überprüfen Sie diese Antwort stackoverflow.com/a/39384160/3933557, die diesem Argument widerspricht. async-await sieht ähnlich aus wie Yield, verwendet jedoch eine Versprechenskette unter der Haube. Bitte teilen Sie mit, wenn Sie eine gute Ressource haben, die besagt, dass "asynchrones Warten als Ertrag angesehen werden kann".
Samarendra

Ich bin mir nicht sicher, wie Sie diese Antwort als "Widerspruch zu diesem Argument" ansehen, weil sie dasselbe sagt wie diese Antwort. > In der Zwischenzeit können Sie mit Transpilern wie Babel asynchron schreiben / warten und den Code in Generatoren konvertieren.
Arnavion

Es heißt, Babel konvertiere zu Generatoren, aber was Sie sagen, ist "Ertrag kann als Baustein des Wartens angesehen werden" und "Async-Warten kann als Ertrag angesehen werden". was nach meinem Verständnis nicht korrekt ist (vorbehaltlich einer Korrektur). async-await verwendet intern Versprechensketten, wie in dieser Antwort erwähnt. Ich möchte verstehen, ob mir etwas fehlt. Können Sie uns bitte Ihre Gedanken dazu mitteilen?
Samarendra

Diese Antwort erhebt nicht den Anspruch, dass alle ES-Motoren auf der ganzen Welt Versprechen intern mithilfe von Generatoren umsetzen. Einige mögen; manche mögen es nicht; Es ist irrelevant für die Frage, auf die dies eine Antwort ist. Trotzdem kann die Art und Weise, wie Versprechen funktionieren, unter Verwendung von Generatoren mit einer bestimmten Art und Weise, den Generator anzutreiben, verstanden werden, und das erklärt diese Antwort.
Arnavion

43

Nun, es stellt sich heraus, dass es eine sehr enge Beziehung zwischen async/ awaitund Generatoren gibt. Und ich glaube async/ awaitwerde immer auf Generatoren bauen. Wenn Sie sich ansehen, wie Babel transpiliert async/ await:

Babel nimmt das:

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);

});

und macht daraus

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);

}));

Du machst die Mathematik.

Dies macht es aussehen wie das asyncSchlüsselwort nur , dass Wrapper - Funktion ist, aber wenn das der Fall ist , dann awaitwird nur verwandelt sich in yield, wird es wahrscheinlich ein bisschen mehr auf das Bild sein später , wenn sie heimisch werden.

Weitere Erklärungen hierzu finden Sie hier: https://www.promisejs.org/generators/


1
NodeJS hat native async / warten seit einer Weile ohne Generatoren: codeforgeek.com/2017/02/…
Bram

3
Die native Implementierung von @Bram verwendet absolut Generatoren unter der Haube, dasselbe, nur abstrahiert.
Alexander Mills

3
Das glaube ich nicht. Async / await ist nativ in der V8-Engine implementiert. Generatoren, bei denen eine ES6-Funktion, async / await, ES7 ist. Es war Teil der 5.5-Version der V8-Engine (die in Node verwendet wird): v8project.blogspot.nl/2016/10/v8-release-55.html . Es ist möglich, ES7 async / await in ES6-Generatoren zu transpilieren, aber mit neuen Versionen von NodeJS wird dies nicht mehr benötigt, und die Leistung von async / await scheint sogar besser zu sein als die von Generatoren: medium.com/@markherhold/…
Bram

1
async / await verwendet Generatoren, um seine Sache zu tun
Alexander Mills

@AlexanderMills können Sie bitte einige legitime Ressourcen teilen, die besagen, dass async / await Generatoren intern verwendet? Überprüfen Sie dies und stackoverflow.com/a/39384160/3933557, was diesem Argument widerspricht. Ich denke, nur weil Babel Generatoren verwendet, heißt das nicht, dass es ähnlich unter der Haube implementiert wird. Irgendwelche Gedanken dazu
Samarendra

28

Was zum Teufel ist der Unterschied zwischen dem awaitSchlüsselwort und dem yieldSchlüsselwort?

Das awaitSchlüsselwort darf nur in async functions verwendet werden, während das yieldSchlüsselwort nur in Generatoren verwendet werden soll function*. Und diese sind natürlich auch anders - der eine gibt Versprechen zurück, der andere gibt Generatoren zurück.

Wird awaitaus etwas immer ein Versprechen, während yieldes keine solche Garantie gibt?

Ja, awaitruft Promise.resolveden erwarteten Wert auf.

yield ergibt nur den Wert außerhalb des Generators.


Ein kleiner Fehler, aber wie ich in meiner Antwort erwähnt habe, verwendet die Spezifikation nicht Promise.resolve (früher), sondern PromiseCapability :: resolve, das vom Promise-Konstruktor genauer dargestellt wird.
Arnavion

@Arnavion: Promise.resolveverwendet genau das gleiche new PromiseCapability(%Promise%), was die async / await-Spezifikation direkt verwendet. Ich dachte nur, es Promise.resolveist besser zu verstehen.
Bergi

1
Promise.resolvehat einen zusätzlichen Kurzschluss "IsPromise == true? dann gleichen Wert zurückgeben", den Async nicht hat. Das heißt, await pwo pein Versprechen ist, wird ein neues Versprechen zurückgegeben, das aufgelöst wird p, während Promise.resolve(p)es zurückkehren würde p.
Arnavion

Oh, das habe ich verpasst - ich dachte, das wäre nur in Promise.castund aus Konsistenzgründen veraltet. Aber es spielt keine Rolle, wir sehen dieses Versprechen sowieso nicht wirklich.
Bergi

2
var r = await p; console.log(r);sollte in etwas wie: transformiert werden p.then(console.log);, während pes als: erstellt werden könnte var p = new Promise(resolve => setTimeout(resolve, 1000, 42));, also ist es falsch zu sagen " Warte auf Aufrufe Promise.resolve", es ist ein anderer Code, der völlig weit von dem Ausdruck " Warten" entfernt ist, der aufruft Promise.resolve, also der transformierte awaitAusdruck dh Promise.then(console.log)aufgerufen und ausgedruckt werden 42.
Dejavu

14

tl; dr

Verwenden Sie async/ await99% der Zeit über Generatoren. Warum?

  1. async/ awaitersetzt direkt den gängigsten Workflow von Versprechensketten, sodass Code als synchron deklariert werden kann, was ihn erheblich vereinfacht.

  2. Generatoren abstrahieren den Anwendungsfall, in dem Sie eine Reihe von asynchronen Operationen aufrufen würden, die voneinander abhängen und sich schließlich in einem "erledigten" Zustand befinden. Das einfachste Beispiel wäre das Durchblättern von Ergebnissen, die schließlich den letzten Satz zurückgeben, aber Sie würden eine Seite nur nach Bedarf aufrufen, nicht unmittelbar nacheinander.

  3. asyncIch awaitbin eigentlich eine Abstraktion, die auf Generatoren aufbaut, um das Arbeiten mit Versprechungen zu erleichtern.

Siehe sehr ausführliche Erläuterung von Async / Await vs. Generators


4

Versuchen Sie, diese Testprogramme , die ich verwendet , um zu verstehen await/ asyncmit Versprechungen.

Programm Nr. 1: Ohne Versprechen läuft es nicht nacheinander

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');

Programm Nr. 2: mit Versprechungen

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

Generatoren sind in vielerlei Hinsicht eine Obermenge von Async / Warten. Im Moment hat async / await sauberere Stack-Traces als co , die beliebteste async / await-ähnliche generatorbasierte lib. Sie können Ihre eigene Variante von Async / Warten mithilfe von Generatoren implementieren und neue Funktionen hinzufügen, z. B. integrierte Unterstützung für yieldNichtversprechen oder Aufbau auf RxJS-Observablen.

Kurz gesagt, Generatoren bieten Ihnen mehr Flexibilität und generatorbasierte Bibliotheken bieten im Allgemeinen mehr Funktionen. Async / await ist jedoch ein zentraler Bestandteil der Sprache. Es ist standardisiert und ändert sich unter Ihnen nicht. Sie benötigen keine Bibliothek, um es zu verwenden. Ich habe einen Blog-Beitrag mit weiteren Details zum Unterschied zwischen Async / Warten und Generatoren.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.