Start · Sprachen · JavaScript · Referenz · async function* expression

async function* expression

Operator

Definiert eine asynchrone Generatorfunktion als Ausdruck, die sowohl <code>await</code> als auch <code>yield</code> verwenden kann und einen <code>AsyncGenerator</code> zurückgibt.

seit JavaScript ES2018 Kategorie: syntax

Signatur

async function* name(params) { /* body */ }

Beschreibung

Der async function*-Ausdruck verbindet asynchrone Funktionen (async function) und Generatorfunktionen (function*) in einer einzigen Syntax. Der Ausdruck ergibt eine Funktion. Ihr Aufruf führt den Funktionskörper noch nicht aus, sondern liefert sofort ein AsyncGenerator-Objekt. Dieses Objekt implementiert sowohl das AsyncIterator- als auch das AsyncIterable-Protokoll.

Im Funktionskörper stehen await und yield zur Verfügung. Mit await wartet die Funktion auf ein Promise, ohne den Hauptthread zu blockieren. yield gibt einen Wert nach außen und hält die Ausführung an, bis der Konsument mit .next() den nächsten Wert anfordert. Jeder Aufruf von .next(), .return() oder .throw() gibt ein Promise auf ein Objekt { value, done } zurück. Mit yield* reicht die Funktion die Iteration an ein anderes synchrones oder asynchrones Iterable weiter.

Typische Einsatzgebiete sind bedarfsgesteuerte (lazy), unendliche oder sehr große Datenströme aus asynchronen Quellen: paginierte API-Antworten, Datenbank-Cursor, WebSocket-Nachrichten oder das zeilenweise Einlesen großer Dateien. Der Konsument durchläuft die Werte meist mit for await...of und verarbeitet jeden Wert, sobald er vorliegt. Weil erst beim nächsten Abruf weitergerechnet wird, entsteht automatisch Gegendruck (Backpressure).

Wie bei anderen Funktionsausdrücken ist der Name optional. Ein angegebener Name ist nur im Funktionskörper selbst sichtbar, zum Beispiel für Rekursion oder für aussagekräftigere Stacktraces. Anders als die async function*-Deklaration wird der Ausdruck nicht gehoben (kein Hoisting). Er kann also erst nach seiner Auswertung benutzt werden. Eine Pfeilfunktions-Variante gibt es nicht.

Parameter

Name Typ Default Beschreibung
$name string (Bezeichner) Optionaler Funktionsname. Er ist nur im Funktionskörper gebunden. Fehlt er, leitet JavaScript name nach Möglichkeit aus der Zuweisung ab.
$params any Formale Parameter. Standardwerte, Destrukturierung und Rest-Parameter (...args) sind erlaubt.
$body Pflicht Anweisungen Funktionskörper mit await, yield, yield* und for await...of. Ein return beendet den Generator mit { value, done: true }.

Rückgabewert

Typ
AsyncGeneratorFunction
Beschreibung
Der Ausdruck ergibt ein Funktionsobjekt vom Typ AsyncGeneratorFunction. Jeder Aufruf liefert ein neues AsyncGenerator-Objekt, dessen Methoden next(), return() und throw() jeweils Promise<{ value, done }> zurückgeben.

Beispiele

Grundlagen: Werte verzögert erzeugen

const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

const countdown = async function* (from) {
  for (let i = from; i > 0; i--) {
    await sleep(200);
    yield i;
  }
  return 'Start!';
};

const gen = countdown(3);
for await (const n of gen) {
  console.log(n);
}
console.log(await gen.next());
3 2 1 { value: undefined, done: true }

Paginierte API bedarfsgesteuert durchlaufen

const fetchAllUsers = async function* (baseUrl) {
  let url = `${baseUrl}/users?page=1`;
  while (url) {
    const res = await fetch(url);
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    const { data, next } = await res.json();
    yield* data;          // jeden Nutzer einzeln ausgeben
    url = next;           // null beendet die Schleife
  }
};

for await (const user of fetchAllUsers('https://api.example.com')) {
  console.log(user.name);
  if (user.name === 'Ada') break; // weitere Seiten werden nie geladen
}

Stream zeilenweise lesen und aufräumen

const lines = async function* (stream) {
  const reader = stream.pipeThrough(new TextDecoderStream()).getReader();
  let rest = '';
  try {
    while (true) {
      const { value, done } = await reader.read();
      if (done) break;
      const parts = (rest + value).split('\n');
      rest = parts.pop();
      yield* parts;
    }
    if (rest) yield rest;
  } finally {
    reader.releaseLock(); // läuft auch bei break/return des Konsumenten
  }
};

const res = await fetch('/log.txt');
let i = 0;
for await (const line of lines(res.body)) {
  console.log(`${++i}: ${line}`);
}
1: ... 2: ...

Rekursion über den internen Namen

const tree = { v: 1, kids: [{ v: 2, kids: [] }, { v: 3, kids: [{ v: 4, kids: [] }] }] };

const walk = async function* visit(node) {
  yield node.v;
  for (const k of node.kids) yield* visit(k);
};

const values = [];
for await (const v of walk(tree)) values.push(v);
console.log(values, walk.name);
[1, 2, 3, 4] visit

// Wichtig · Fallstricke

  • Kein Hoisting: Anders als die Deklaration steht der Ausdruck erst nach seiner Auswertung zur Verfügung. Steht er am Anfang einer Anweisung, wird er als Deklaration gelesen. Dann ist ein Name Pflicht, oder man setzt den Ausdruck in Klammern.
  • Keine Pfeilfunktion und kein Konstruktor: Die Syntax async *() => {} gibt es nicht. new auf eine async-Generatorfunktion löst einen TypeError aus.
  • Lazy: Der Aufruf startet nichts. Der Code läuft erst beim ersten next(). Fehler vor dem ersten yield treten deshalb erst beim Iterieren auf.
  • yield wartet automatisch: yield promise wartet das Promise ab und gibt den aufgelösten Wert aus. Wird das Promise abgelehnt, wird der Fehler im Generator geworfen.
  • Aufräumen: break, return oder eine Ausnahme im for await-Konsumenten ruft return() auf. finally-Blöcke im Generator laufen dann. Dort gehören Verbindungen, Reader und Timer freigegeben.
  • Sequenziell: Aufrufe von next() werden in eine Warteschlange gestellt und nacheinander abgearbeitet. Für parallele Anfragen braucht man eigene Logik, zum Beispiel Promise.all über vorab gestartete Promises.
  • Kompatibilität: Seit ES2018 in allen modernen Browsern und in Node.js ab Version 10 verfügbar. Ältere Umgebungen brauchen eine Transpilierung, etwa mit Babel und regenerator.
  • Mit Array.fromAsync() (ES2024) lässt sich ein endlicher AsyncGenerator bequem in ein Array umwandeln.