JavaScript Generator-Funktionen
JavaScript-Generatoren lernen: function*-Syntax, yield und yield*, Werte mit next(), return() und throw() senden, lazy infinite Sequenzen und Iterables.
Ein Generator ist eine besondere Art von Funktion, die sich mitten in der Ausführung anhalten kann, dem Aufrufer einen Wert zurückgibt und dann genau dort weitermacht, wo sie aufgehört hat, wenn das nächste Mal nach mehr gefragt wird. Anstatt alles im Voraus zu berechnen und einmal zurückzugeben, erzeugt ein Generator eine Folge von Werten träge — einen nach dem anderen, nur wenn angefordert.
Diese eine Idee macht Generatoren zum saubersten Weg in JavaScript, um benutzerdefinierte Iterables zu erstellen, unendliche Sequenzen ohne Speicherverschwendung abzubilden und langlaufende Logik auf Anfrage durchzugehen. Dieser Leitfaden behandelt die function*-Syntax, yield und yield*, die bidirektionale Kommunikation mit next(), das vorzeitige Beenden und die praktischen Muster, in denen Generatoren glänzen.
Diese Seite baut auf Iterables und Iteratoren auf — wenn das Iterator-Protokoll neu für Sie ist, lesen Sie das zuerst.
Generator-Funktionen verstehen
Die Grundlagen von Generator-Funktionen
Eine Generator-Funktion wird wie eine normale Funktion deklariert, aber mit einem Stern nach dem function-Schlüsselwort: function*. Der Stern ist das, was die Funktion als Generator kennzeichnet.
Der Aufruf einer Generator-Funktion führt ihren Rumpf nicht aus. Stattdessen gibt sie ein Generator-Objekt zurück — einen Iterator, den Sie manuell steuern. Jeder Aufruf seiner next()-Methode führt den Rumpf bis zum nächsten yield aus, hält dann an und gibt { value, done } zurück:
Das erste next() läuft bis zum ersten yield 'Hello' und hält dort an. Das zweite setzt nach dieser Zeile fort und läuft bis yield 'World'. Das dritte setzt fort, findet keine weiteren yield-Anweisungen, erreicht das Ende der Funktion und meldet done: true. Weitere Aufrufe von next() würden weiterhin { value: undefined, done: true } zurückgeben.
Da ein Generator-Objekt auch iterierbar ist, können Sie es mit for...of, dem Spread-Operator oder Destructuring verwenden — all das hört automatisch auf, wenn done zu true wird:
Ein Generator-Objekt kann nur einmal iteriert werden. Nachdem es erschöpft ist, liefert for...of nichts. Rufen Sie die Generator-Funktion erneut auf, um einen frischen Generator zu erhalten.
Der return-Wert eines Generators
Ein return innerhalb eines Generators beendet die Iteration und liefert den endgültigen value zusammen mit done: true. Beachten Sie, dass for...of diesen zurückgegebenen Wert ignoriert — es sieht nur die mit yield erzeugten:
Den Ablauf mit return() und throw() steuern
Neben next() bietet ein Generator-Objekt zwei weitere Methoden, die es dem Aufrufer ermöglichen, die Ausführung von außen zu steuern:
generator.return(value)zwingt den Generator, sofort zu beenden, führt dabei allefinally-Blöcke aus und gibt{ value, done: true }zurück.generator.throw(error)injiziert eine Ausnahme am aktuellenyield-Punkt, sodass der Generator sie mittry...catchabfangen kann — oder sie weiterleitet, wenn er das nicht tut.
Der finally-Block macht Generatoren zu einem ordentlichen Ort, um Ressourcen freizugeben (eine Datei schließen, einen Stream trennen), auch wenn die Iteration vorzeitig abgebrochen wird.
Fortgeschrittene Generator-Muster
Delegieren mit yield*
Wenn ein Generator die Werte eines anderen Generators oder eines beliebigen Iterables erzeugen muss, verwenden Sie yield* (ausgesprochen „yield-delegate") anstatt eine manuelle Schleife zu schreiben. Es leitet jeden Wert aus der delegierten Quelle transparent weiter:
Ein nützliches Detail: Der Ausdruckswert von yield* ist der return-Wert des inneren Generators, was es ermöglicht, Generatoren zu kombinieren und ein Ergebnis an den äußeren zurückzugeben:
Werte in Generatoren senden
Die Kommunikation mit einem Generator ist bidirektional. Ein yield-Ausdruck sendet nicht nur einen Wert nach außen über next(), er wird auch zu dem, was Sie beim nächsten next(value)-Aufruf hineingeben. Das verwandelt einen Generator in eine kleine interaktive Coroutine:
Das erste next() läuft bis zum ersten yield und gibt die Frage zurück; sein Argument wird verworfen, weil noch kein pausiertes yield vorhanden ist, das es empfangen könnte. Das zweite next('Alice') setzt das pausierte yield fort, sodass der Ausdruck zu 'Alice' wird und name zugewiesen wird.
Praktische Anwendungen von JavaScript-Generatoren
Benutzerdefinierte Iterables erstellen
Die häufigste reale Anwendung von Generatoren ist es, eigene Objekte iterierbar zu machen. Definieren Sie [Symbol.iterator] als Generator-Methode, und jedes for...of, Spread oder Destructuring wird auf dem Objekt funktionieren. Das erfordert weit weniger Boilerplate als das Schreiben eines Iterator-Objekts mit einem handgestrickten next() (siehe Symbol-Typ für die Bedeutung von Symbol.iterator):
Lazy und unendliche Sequenzen
Da ein Generator jeden Wert nur bei Bedarf berechnet, kann er eine konzeptuell unendliche Sequenz beschreiben, ohne jemals den Speicher zu erschöpfen. Das klassische Beispiel ist ein Zähler, aber derselbe Ansatz eignet sich für Bereiche, ID-Generatoren oder Fibonacci-Ströme. Man kombiniert ihn mit einem „take"-Helfer, der nach der benötigten Anzahl stoppt:
Ein parametrisierter Bereichsgenerator ist eine praktische, wiederverwendbare Variante desselben Gedankens:
Asynchrone Operationen verwalten
Historisch gesehen wurden Generatoren verwendet, um „Callback-Hell" zu glätten: Jedes yield pausierte bei einem Promise, und ein externer Runner setzte den Generator fort, sobald das Promise aufgelöst wurde. Das folgende Beispiel zeigt die manuelle Mechanik, damit Sie sehen können, was unter der Haube passiert:
Dies ist ein vereinfachter manueller Runner. Im modernen Code würden Sie das mit async/await schreiben, das die Sprache direkt auf diesem Generator-plus-Promise-Muster aufgebaut hat. Für asynchrone Iteration (streaming von Chunks über die Zeit) verwenden Sie async generators mit async function* und for await...of.
Generatoren vs. reguläre Funktionen
| Aspekt | Reguläre Funktion | Generator-Funktion |
|---|---|---|
| Deklaration | function fn() | function* fn() |
| Aufruf | führt den Rumpf vollständig aus | gibt ein Generator-Objekt zurück, führt noch nichts aus |
| Gibt zurück | einen einzelnen Wert | einen Strom von Werten via yield |
| Kann pausieren? | nein | ja, bei jedem yield |
| Iterierbar? | nein | ja (funktioniert mit for...of, Spread) |
Greifen Sie auf einen Generator zurück, wenn Sie Werte träge oder auf Anfrage produzieren müssen, ein Objekt iterierbar sein soll oder eine Sequenz potenziell unendlich ist. Für eine einfache einmalige Berechnung ist eine reguläre Funktion einfacher und schneller.
Fazit
Generatoren geben JavaScript eine saubere Möglichkeit, Sequenzen träge zu produzieren, Logik anzuhalten und fortzusetzen und jedes Objekt mit Schleifen wie for...of kompatibel zu machen. Beherrschen Sie yield zum Ausgeben von Werten, yield* zum Delegieren an andere Iterables und next()/return()/throw() zum Steuern und Kontrollieren des Ablaufs — und Sie haben ein Werkzeug, das alles antreibt, von benutzerdefinierten Iterables bis zur darauf aufbauenden async/await-Syntax.