Back

Anwendungsfälle für JavaScript-Generatoren

Anwendungsfälle für JavaScript-Generatoren

JavaScript-Generatoren (function*) sind seit ES2015 Teil der Sprache, dennoch greifen viele Frontend-Entwickler immer noch zu Arrays oder Promise-Ketten, obwohl Generatoren die sauberere Lösung wären. Der Kernvorteil liegt nicht in der reinen Geschwindigkeit – es geht um Lazy Evaluation, Komponierbarkeit und präzise Kontrolle über die Iteration. Dieser Artikel behandelt Situationen, in denen Generatoren ihren Platz in modernem Frontend-Code wirklich verdienen.

Wichtigste Erkenntnisse

  • Generatoren erzeugen Werte bei Bedarf durch Lazy Evaluation und vermeiden so unnötige Berechnungen und Zwischen-Arrays
  • Die Iterator Helpers API bringt eingebaute map-, filter- und take-Methoden für von Generatoren zurückgegebene Iteratoren und macht benutzerdefinierte Hilfsfunktionen überflüssig
  • yield* macht die rekursive Traversierung von Bäumen und Graphen sowohl lesbar als auch lazy
  • Asynchrone Generatoren (async function*) arbeiten zusammen mit for await...of, um paginierte oder stapelweise Datenabfragen mit minimalem State-Management zu handhaben

Was JavaScript-Generatoren unterscheidet

Eine Generatorfunktion gibt einen Iterator zurück. Der Aufruf der Funktion führt keinen Code aus – sie übergibt Ihnen ein Objekt mit einer .next()-Methode. Jeder Aufruf von .next() führt den Funktionskörper bis zum nächsten yield aus und pausiert dann, wobei der lokale Zustand über Aufrufe hinweg erhalten bleibt.

function* range(start, end) {
  for (let i = start; i < end; i++) yield i
}

for (const n of range(0, 5)) {
  console.log(n) // 0, 1, 2, 3, 4
}

Da Generatoren das Iterator-Protokoll implementieren, funktionieren sie direkt mit for...of, Spread-Syntax und Destructuring – ohne Adapter.

Lazy Iteration in JavaScript: Datenverarbeitung ohne Materialisierung

Der Hauptgrund, einen Generator statt eines Arrays zu verwenden, ist Lazy Iteration: Werte werden nur bei Bedarf erzeugt. Das ist wichtig, wenn:

  • Der vollständige Datensatz groß ist und Sie nur einen Teil davon benötigen
  • Die Berechnung jedes Werts aufwändig ist
  • Die Sequenz konzeptionell unendlich ist
function* naturals() {
  let n = 0
  while (true) yield n++
}

// Berechnet nur Werte bis zum Break-Point
for (const n of naturals()) {
  if (n > 100) break
}

Es wird kein Zwischen-Array erstellt. Es werden keine Werte über den Break-Point hinaus berechnet.

Die Iterator Helpers API: Eingebaute Lazy Pipelines

Das Schreiben benutzerdefinierter map-, filter- und take-Hilfsfunktionen war früher notwendiger Boilerplate-Code. Die Iterator Helpers API – jetzt in allen modernen Browsern verfügbar – fügt diese direkt zu synchronen Iteratoren hinzu:

const result = naturals()
  .filter(n => n % 2 === 0)
  .map(n => n * n)
  .take(5)
  .toArray() // [0, 4, 16, 36, 64]

Jeder Schritt ist lazy. .toArray() ist das, was die Auswertung auslöst. Dies macht Generator-basierte Pipelines deutlich sauberer ohne Drittanbieter-Bibliotheken. Beachten Sie, dass diese Helfer für synchrone Iteratoren gelten – asynchrone Iterator-Helfer sind noch nicht umgebungsübergreifend standardisiert.

Baum- und Graphen-Traversierung

Generatoren sind eine natürliche Lösung für die Traversierung rekursiver Strukturen. Die Tiefensuche in einem DOM-ähnlichen Baum wird unkompliziert:

function* walkTree(node) {
  yield node
  for (const child of node.children ?? []) {
    yield* walkTree(child)
  }
}

for (const node of walkTree(rootNode)) {
  if (node.type === 'input') processInput(node)
}

yield* delegiert an einen verschachtelten Generator, hält die Rekursion lesbar und die Traversierung lazy – Sie stoppen, sobald Sie finden, was Sie brauchen.

Asynchrone Generatoren in JavaScript: Paginierte und stapelweise Datenabfrage

async function* erweitert das Muster auf asynchrone Sequenzen. In Kombination mit for await...of eignet es sich gut für paginierte API-Antworten:

async function* fetchPages(url) {
  let nextUrl = url
  while (nextUrl) {
    const res = await fetch(nextUrl)
    const data = await res.json()
    yield data.items
    nextUrl = data.nextPage ?? null
  }
}

for await (const batch of fetchPages('/api/records')) {
  processBatch(batch)
}

Jede Seite wird nur abgerufen, wenn die Schleife fortschreitet. Es ist nicht nötig, alle Seiten im Voraus zu sammeln oder den Paginierungszustand extern zu verwalten – der Generator hält ihn.

Wann man Generatoren nicht verwenden sollte

Generatoren fügen Indirektion hinzu. Für eine einfache Array-Transformation, die Sie vollständig verbrauchen werden, sind verkettete Array-Methoden klarer. Verwenden Sie Generatoren, wenn die Sequenz groß, potenziell unendlich ist oder wenn Sie früh stoppen müssen, ohne verschwendete Berechnungen.

Fazit

JavaScript-Generatoren glänzen in drei Bereichen: Lazy Iteration über große oder unendliche Sequenzen, komponierbare Daten-Pipelines (besonders mit der Iterator Helpers API) und asynchrone Datenabfrage, bei der Sie sequenzielle, zustandsbehaftete Kontrolle benötigen. Sie sind kein Ersatz für Arrays oder async/await – sie sind das richtige Werkzeug, wenn Sie Werte bei Bedarf erzeugen müssen, anstatt alle auf einmal.

FAQs

Ja. Generatoren funktionieren gut, um Datensequenzen zu erzeugen, die React-Komponenten konsumieren. Sie können einen Generator innerhalb eines useEffect- oder useMemo-Hooks aufrufen, um Werte lazy zu berechnen. Verwenden Sie jedoch keinen Generator als Komponentenfunktion selbst – React erwartet, dass Komponenten JSX zurückgeben, keine Iteratoren.

Der Generator bleibt an seinem letzten yield-Punkt pausiert. Er wird für die Garbage Collection freigegeben, sobald keine Referenzen auf sein Iterator-Objekt mehr bestehen. Wenn Sie Cleanup-Logik ausführen müssen, wenn die Iteration vorzeitig stoppt, umschließen Sie das yield mit einem try-finally-Block. Der finally-Block wird ausgeführt, wenn die return-Methode des Iterators aufgerufen wird oder der Generator vom Garbage Collector erfasst wird.

Bei kleinen, vollständig konsumierten Collections haben Generatoren einen leichten Overhead durch den Pause-und-Resume-Mechanismus. Der Performance-Unterschied ist in den meisten Anwendungen vernachlässigbar. Generatoren werden in der Praxis schneller, wenn Sie große Datensätze teilweise verarbeiten, da sie die Allokation von Zwischen-Arrays vermeiden und die Berechnung von Werten überspringen, die Sie nie anfordern.

Ein asynchroner Generator liefert Werte inkrementell, sobald sie verfügbar werden, während ein Promise-basierter Ansatz wartet, bis alle Daten gesammelt sind, bevor er zurückkehrt. Das bedeutet, dass asynchrone Generatoren es Ihnen ermöglichen, sofort mit der Verarbeitung des ersten Ergebnis-Batches zu beginnen, den Spitzenspeicherverbrauch zu reduzieren und Ihnen feinere Kontrolle darüber zu geben, wann jeder nachfolgende Abruf erfolgt.

Complete picture for complete understanding

Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.

Check our GitHub repo and join the thousands of developers in our community.

OpenReplay