Wenn Sie in den letzten zwei bis fünf Jahren als JavaScript-Entwickler tätig waren, sind Sie bestimmt schon auf Beiträge gestoßen, in denen von Generatoren und Iteratoren die Rede war. Während Generatoren und Iteratoren eng miteinander verbunden sind, scheinen Generatoren etwas einschüchternder zu sein als die anderen.
Iteratoren sind eine Implementierung von Iterable-Objekten wie Maps, Arrays und Strings, die es uns ermöglicht, mit next() über sie zu iterieren. Sie haben eine Vielzahl von Anwendungsfällen über Generatoren, Observables und Spread-Operatoren.
Ich empfehle den folgenden Link für diejenigen unter Ihnen, die neu in Iteratoren sind, Guide to Iterators.
Um zu überprüfen, ob Ihr Objekt mit dem Iterable-Protokoll konform ist, verwenden Sie das eingebaute Symbol.iterator:
Generatoren, die als Teil von ES6 eingeführt wurden, haben keine Änderungen für die weiteren JavaScript-Versionen erfahren und sie sind hier, um länger zu bleiben. Ich meine wirklich lange! Es gibt also kein Entrinnen davor. Obwohl ES7 und ES8 einige neue Aktualisierungen aufweisen, haben sie nicht das gleiche Ausmaß an Veränderungen wie ES6 gegenüber ES5, das JavaScript sozusagen auf die nächste Stufe gebracht hat.
Am Ende dieses Beitrags bin ich sicher, dass Sie ein solides Verständnis davon haben, wie Funktionsgeneratoren funktionieren. Wenn Sie ein Profi sind, helfen Sie mir bitte, den Inhalt zu verbessern, indem Sie Ihre Kommentare in den Antworten hinzufügen. Nur für den Fall, dass Sie Schwierigkeiten haben, dem Code zu folgen, habe ich auch Erklärungen für die meisten der Codes hinzugefügt, um Ihnen zu helfen, besser zu verstehen.
Funktionen in JavaScript, wie wir alle wissen, „laufen bis zum Return/End“. Generatorfunktionen hingegen „laufen bis yield/return/end“. Im Gegensatz zu normalen Funktionen geben Generator-Funktionen, sobald sie aufgerufen werden, das Generator-Objekt zurück, das das gesamte Generator-Iterable enthält, das mit der next()-Methode oder der for…-Schleife iteriert werden kann.
Jeder next()-Aufruf des Generators führt jede Codezeile bis zum nächsten Yield aus, auf den er trifft, und setzt seine Ausführung vorübergehend aus.
Syntaktisch werden sie mit einem * gekennzeichnet, entweder Funktion* X oder Funktion *X, – beide bedeuten dasselbe.
Nach der Erstellung gibt der Aufruf der Generatorfunktion das Generatorobjekt zurück. Dieses Generator-Objekt muss einer Variablen zugewiesen werden, um die nachfolgenden next()-Methoden zu verfolgen, die auf ihm selbst aufgerufen werden. Wenn der Generator nicht einer Variablen zugewiesen ist, wird er immer nur bis zum ersten Yield-Ausdruck bei jedem next()-Aufruf zurückgeben.
Die Generatorfunktionen werden normalerweise mit Yield-Ausdrücken aufgebaut. Jeder Yield-Ausdruck innerhalb der Generatorfunktion ist ein Haltepunkt, bevor der nächste Ausführungszyklus beginnt. Jeder Ausführungszyklus wird durch die next()-Methode des Generators ausgelöst.
Bei jedem next()-Aufruf gibt der yield-Ausdruck seinen Wert in Form eines Objekts zurück, das die folgenden Parameter enthält.
{ value: 10, done: false } // assuming that 10 is the value of yield
- Wert – ist alles, was auf der rechten Seite des yield-Schlüsselworts steht, es kann ein Funktionsaufruf, ein Objekt oder praktisch alles sein. Bei leeren Yields ist dieser Wert undefiniert.
- Done – gibt den Status des Generators an, ob er weiter ausgeführt werden kann oder nicht. Wenn done den Wert true liefert, bedeutet dies, dass die Funktion ihren Lauf beendet hat.
(Wenn Sie das Gefühl haben, dass das ein bisschen zu hoch für Sie ist, werden Sie mehr Klarheit bekommen, wenn Sie das Beispiel unten sehen…)
Anmerkung: Im obigen Beispiel wird die Generatorfunktion, auf die direkt ohne Wrapper zugegriffen wird, immer nur bis zum ersten Yield ausgeführt. Daher müssen Sie den Generator per Definition einer Variablen zuweisen, um ihn korrekt zu iterieren.
Lebenszyklus einer Generatorfunktion
Bevor wir fortfahren, werfen wir einen kurzen Blick auf das Blockdiagramm des Lebenszyklus einer Generatorfunktion:
Jedes Mal, wenn eine Ausbeute angetroffen wird, gibt die Generatorfunktion ein Objekt zurück, das den Wert der angetroffenen Ausbeute und den Status done enthält. In ähnlicher Weise erhalten wir beim Auftreten eines Returns den Rückgabewert und den Status done als true. Wann immer der Status done als true zurückgegeben wird, bedeutet das im Wesentlichen, dass die Generatorfunktion ihren Lauf abgeschlossen hat und kein weiterer Yield möglich ist.
Alles nach dem ersten Return wird ignoriert, einschließlich anderer Yield-Ausdrücke.
Lesen Sie weiter, um das Blockdiagramm besser zu verstehen.
Yield einer Variablen zuweisen
Im vorherigen Codebeispiel haben wir eine Einführung in die Erstellung eines einfachen Generators mit einem Yield gesehen. Und haben die erwartete Ausgabe erhalten. Nehmen wir nun an, dass wir im folgenden Code den gesamten Yield-Ausdruck einer Variablen zuweisen.
Was ist das Ergebnis des gesamten Yield-Ausdrucks, der an die Variable übergeben wurde? Nichts oder Undefiniert …
Warum ? Ab der zweiten next() wird das vorherige Yield durch die in der nächsten Funktion übergebenen Argumente ersetzt. Da wir hier in der next-Methode nichts übergeben, wird davon ausgegangen, dass der gesamte ‚previous-yield-Ausdruck‘ undefiniert ist.
Mit diesem Wissen springen wir zum nächsten Abschnitt, um mehr über die Übergabe von Argumenten an die next()-Methode zu erfahren.
Übergabe von Argumenten an die next()-Methode
Mit Bezug auf das obige Blockdiagramm lassen Sie uns über die Übergabe von Argumenten an die next-Funktion sprechen. Dies ist einer der kniffligsten Teile der gesamten Generator-Implementierung.
Betrachten wir das folgende Codestück, bei dem die Ausbeute einer Variablen zugewiesen wird, aber dieses Mal übergeben wir einen Wert in der next()-Methode.
Schauen wir uns den Code unten in der Konsole an. Und die Erklärung gleich danach.
Erklärung:
- Wenn wir die erste next(20) aufrufen, wird jede Codezeile bis zum ersten Yield gedruckt. Da wir keinen vorherigen Yield-Ausdruck haben, wird der Wert 20 verworfen. In der Ausgabe erhalten wir den Yield-Wert i*10, der hier 100 ist. Der zweite next(10)-Aufruf ersetzt den gesamten ersten yield-Ausdruck durch 10, also yield (i * 10) = 10, und setzt den Wert von const j auf 50, bevor der Wert des zweiten yields zurückgegeben wird. Der Yield-Wert ist hier 2 * 50 / 4 = 25.
- Das dritte next(5) ersetzt den gesamten zweiten Yield-Ausdruck durch 5, wodurch der Wert von k auf 5 gesetzt wird. Und weiter geht es mit der return-Anweisung, die (x + y + z) => (10 + 50 + 5) = 65 als letzten yield-Wert zusammen mit done true zurückgibt.
Das mag für den Erstleser etwas überwältigend sein, aber nimm dir gut 5 Minuten Zeit, um es immer wieder zu lesen, um es vollständig zu verstehen.
Weitergabe von Yield als Argument einer Funktion
Es gibt n-fache Anwendungsfälle für Yield, wie es in einem Funktionsgenerator verwendet werden kann. Schauen wir uns den folgenden Code für eine solche interessante Verwendung von yield an, zusammen mit einer Erklärung.
Erklärung
- Das erste next() liefert einen undefinierten Wert, da der Ausdruck yield keinen Wert hat.
- Das zweite next() liefert „I am useless“, den Wert, der übergeben wurde. Und bereitet das Argument für den Funktionsaufruf vor.
- Das dritte next(), ruft die Funktion mit einem undefinierten Argument auf. Wie bereits erwähnt, bedeutet der Aufruf der next()-Methode ohne Argumente im Wesentlichen, dass der gesamte vorherige yield-Ausdruck undefiniert ist. Daher wird undefiniert ausgegeben und der Lauf beendet.
Yield mit einem Funktionsaufruf
Neben der Rückgabe von Werten kann yield auch Funktionen aufrufen und den Wert zurückgeben oder ausgeben. Schauen wir uns den folgenden Code an, um ihn besser zu verstehen.
Der obige Code gibt den Rückgabewert der Funktion obj als Yield-Wert zurück.
Yield mit Promises
Yield mit Promises verfolgt den gleichen Ansatz wie der obige Funktionsaufruf, statt einen Wert von der Funktion zurückzugeben, wird ein Promise zurückgegeben, das bei Erfolg oder Misserfolg weiter ausgewertet werden kann. Schauen wir uns den Code unten an, um zu verstehen, wie es funktioniert.
Der apiCall gibt die Versprechen als Yield-Wert zurück, wenn er nach 2 Sekunden aufgelöst wird, gibt er den benötigten Wert aus.
Yield*
Bislang haben wir uns die Anwendungsfälle für Yield-Ausdrücke angesehen, jetzt werden wir uns einen weiteren Ausdruck namens Yield* ansehen. Yield* wird innerhalb einer Generatorfunktion verwendet und delegiert eine andere Generatorfunktion. Einfach ausgedrückt, führt es die Generatorfunktion in seinem Ausdruck synchron aus, bevor es zur nächsten Zeile übergeht.
Schauen wir uns den Code und die Erklärung unten an, um ihn besser zu verstehen. Dieser Code stammt aus den MDN-Webdocs.
Der endgültige Rückgabewert wird nicht ausgegeben, da die for…of-Schleife nur bis zur letzten Rückgabe iteriert. Es gehört also zu den besten Praktiken, Return-Anweisungen innerhalb einer Generatorfunktion zu vermeiden, da sie die Wiederverwendbarkeit der Funktion beeinträchtigen würden, wenn sie über eine for…of-Schleife iteriert wird.
Abschluss
Ich hoffe, dies deckt die grundlegenden Anwendungsfälle von Generatorfunktionen ab und ich hoffe aufrichtig, dass es ein besseres Verständnis dafür vermittelt hat, wie Generatoren in JavaScript ES6 und höher funktionieren. Wenn dir mein Inhalt gefällt, hinterlasse bitte 1, 2, 3 oder sogar 50 Klatschen :).
Bitte folge mir auf meinem GitHub-Konto für weitere JavaScript- und Full-Stack-Projekte: