Eine async-Funktion in TypeScript gibt immer ein Promise zurück: Gibt ihr Rumpf einen string zurück, ist ihr Typ Promise<string>. Darin packt await ein Promise<T> zu einem T aus.
Das Laufzeitverhalten (was await pausiert, die Event Loop) ist JavaScript und wird unter async/await in JavaScript behandelt. Der Teil von TypeScript sind die Typen, die hinein und herausgehen.
Rückgabetypen von async-Funktionen
Der deklarierte Rückgabetyp einer async-Funktion muss Promise<...> sein, auch wenn der Rumpf den einfachen Wert zurückgibt. Lässt du ihn weg, leitet TypeScript ihn ab.
async function count(): number zu schreiben ist der Fehler TS1064 (The return type of an async function or method must be the global Promise<T> type. Did you mean to write 'Promise<number>'?). Der Utility Type Awaited<T> liefert den ausgepackten Typ: Awaited<ReturnType<typeof count>> ist number.
await vergessen
Ein fehlendes await lässt dich mit einem Promise<T> statt einem T zurück. TypeScript findet die meisten dieser Fälle, weil die Typen nicht mehr passen:
Der Bug mit if (ok) ist echt: Ein Promise-Objekt ist immer truthy, also wurde der Zugriff gewährt. TypeScript meldet ihn als TS2801. Das Promise einer boolean-Variablen zuzuweisen oder eine Eigenschaft zu lesen, die das Promise nicht hat, würde ebenfalls nicht kompilieren. Ein Aufruf, dessen Ergebnis du ignorierst (save(user);), wird nicht gefunden; das deckt die Regel no-floating-promises von typescript-eslint ab.
Fehlerbehandlung mit try/catch
Ein abgelehntes Promise lässt await werfen, also funktioniert ein gewöhnliches try/catch. Unter strict ist die Variable in catch unknown, und du engst sie ein, bevor du .message liest:
throw in einer async-Funktion lehnt ihr Promise ab, statt an der Aufrufstelle zu werfen. Weitere Muster, darunter eigene Fehlerklassen und Ergebnisse zurückgeben statt zu werfen, stehen auf der Seite zur Fehlerbehandlung.
Top-Level await
await außerhalb jeder Funktion funktioniert nur in einem ES-Modul. Eine als CommonJS kompilierte Datei (der Fall bei diesen Beispielen und bei Node-Projekten ohne "type": "module") lehnt es ab:
index.ts(2,14): error TS1309: The current file is a CommonJS module and cannot use 'await' at the top level.
Packe den Code in eine async-Funktion main und rufe sie auf, wie es jedes Beispiel auf dieser Seite tut. In einem Projekt mit ES-Modulen ("type": "module" in package.json mit module auf node16 oder nodenext, oder module: "esnext" für einen Bundler) ist Top-Level await erlaubt.
Sequenziell vs parallel
Jedes await wartet auf sein Promise, bevor die nächste Zeile beginnt. Bei unabhängigen Aufrufen startest du sie zuerst alle und wartest dann gemeinsam mit Promise.all:
In der sequenziellen Hälfte kann b nicht vor a fertig werden, weil es noch nicht gestartet ist. In der parallelen Hälfte wird d zuerst fertig, und die Gesamtzeit entspricht ungefähr dem längsten Aufruf statt der Summe. Promise.all liefert die Ergebnisse trotzdem in der Reihenfolge der Eingaben, als Tupel typisiert.
Die forEach-Falle
forEach ignoriert das Promise, das ein async-Callback zurückgibt, also wartet nichts auf die Arbeit:
for...of mit await verarbeitet die Elemente einzeln nacheinander; Promise.all mit map führt sie parallel aus und wartet auf alle. forEach tut keins von beiden, und TypeScript warnt nicht, weil ein Callback, der als Rückgabe void typisiert ist, einen akzeptiert, der ein Promise zurückgibt.
Asynchrone Iteration mit for await
for await...of durchläuft ein asynchrones Iterable, etwa einen async Generator, und wartet auf jeden Wert:
async function* pages(total: number): AsyncGenerator<string[]> {
for (let page = 1; page <= total; page++) {
await new Promise((r) => setTimeout(r, 10));
yield [`item ${page}a`, `item ${page}b`];
}
}
async function main() {
for await (const batch of pages(3)) {
console.log(batch.join(", ")); // batch: string[]
}
}
main();
Der Elementtyp kommt aus der Annotation AsyncGenerator<T> des Generators oder aus der Inferenz, wenn du sie weglässt.
Häufig gestellte Fragen
Welchen Rückgabetyp hat eine async-Funktion in TypeScript?
Immer ein Promise. Eine async-Funktion, die eine number zurückgibt, hat den Rückgabetyp Promise<number>, und eine ohne Rückgabewert hat Promise<void>. async function f(): number zu schreiben ist der Fehler TS1064, der Promise<number> vorschlägt.
Wie verwende ich await auf oberster Ebene in TypeScript?
Top-Level await funktioniert nur in einem ES-Modul, mit module auf es2022, esnext, system, preserve oder node16/node18/node20/nodenext in einer Datei, die Node als ESM behandelt, und target ab es2017. In einer CommonJS-Datei ist es der Fehler TS1309. Die portable Lösung ist eine async-Funktion main: async function main() { ... } main();.
Wie behandle ich Fehler mit async/await in TypeScript?
Setze das await in try/catch. Unter strict ist der gefangene Wert als unknown typisiert, enge ihn also zuerst ein: if (e instanceof Error) console.log(e.message). Eine Ablehnung, auf die nie gewartet und die nie gefangen wird, wird zu einer Unhandled Rejection.
Wie führe ich in TypeScript asynchrone Aufrufe parallel aus?
Starte zuerst alle Promises und warte dann gemeinsam darauf: const [a, b] = await Promise.all([loadA(), loadB()]). await loadA(); await loadB(); führt sie nacheinander aus. Promise.all behält den Typ jedes Ergebnisses im resultierenden Tupel.
Warum funktioniert async in forEach nicht?
forEach ruft den Callback auf und ignoriert, was er zurückgibt, auf die Promises eines async-Callbacks wird also nie gewartet: Die Schleife endet sofort, und Code danach läuft, bevor die Arbeit erledigt ist. Nimm for...of mit await für Arbeit nacheinander oder await Promise.all(items.map(async (x) => ...)) für parallele Arbeit.