Signatur
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
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());
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}`);
}
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);
// 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.newauf eine async-Generatorfunktion löst einenTypeErroraus. - Lazy: Der Aufruf startet nichts. Der Code läuft erst beim ersten
next(). Fehler vor dem erstenyieldtreten deshalb erst beim Iterieren auf. yieldwartet automatisch:yield promisewartet das Promise ab und gibt den aufgelösten Wert aus. Wird das Promise abgelehnt, wird der Fehler im Generator geworfen.- Aufräumen:
break,returnoder eine Ausnahme imfor await-Konsumenten ruftreturn()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 BeispielPromise.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.