3.2 Asynchrones JavaScript: async/await und die typischen Fallen
Nach dieser Einheit kannst du:
- asynchrone Abläufe mit
async/awaitschreiben, mittry/catch/finallyabsichern und in TypeScript mitPromise<T>typisieren. - die Ausgabereihenfolge von Code mit
awaitvorhersagen und Callbacks, Promise-Kette undasync/awaitvergleichen. - ein vergessenes
awaitund ein Floating Promise an ihren Symptomen erkennen und mitawait,.catch(...)odervoidsowie der Biome-RegelnoFloatingPromisesbeheben.
Du klickst in einer App auf „Speichern“, und sofort erscheint „gespeichert“. Eine Sekunde später scheitert die Verbindung zum Server. In der Konsole steht eine rote Zeile, die niemand liest, und deine Eingaben sind weg. Kein Absturz, keine Meldung auf dem Bildschirm, nur ein fehlendes Wort: await.
flowchart LR
accTitle: Ein Promise ohne await scheitert unbemerkt
btn["Klick auf Speichern"] --> save["saveReport();<br/>ohne await"]
save --> shown["Anzeige: gespeichert"]
save -.-> fail["später: Server<br/>nicht erreichbar"]
fail --> nobody["niemand fängt<br/>den Fehler"]
Abb. 3.8: Das Programm meldet Erfolg, bevor das Speichern überhaupt fertig ist (eigene Darstellung).
In Einheit 3.1 hast du das Modell gebaut: Event Loop, zwei Warteschlangen und Promises. Diese Einheit zeigt zuerst async/await, die Schreibweise, mit der asynchroner Code wie gewöhnlicher Code aussieht. Gerade weil er so harmlos aussieht, übersieht man leicht ein fehlendes await. Deshalb folgen gleich danach die typischen Fallen.
Begriffe vorab
Abschnitt betitelt „Begriffe vorab“async-Funktion: eine Funktion, die mitasyncmarkiert ist und immer ein Promise liefert.await: wartet in einerasync-Funktion auf ein Promise und liefert dessen Wert.- Floating Promise: das Promise einer aufgerufenen
async-Funktion, das niemand erwartet und dessen Fehler niemand fängt. - Unhandled Rejection: ein abgelehntes Promise ohne Fehlerbehandlung. Node.js beendet dann das Programm.
- Nursery-Regel: eine Linter-Regel in Erprobung, die du in Biome ausdrücklich einschalten musst.
async und await: asynchron schreiben wie synchron
Abschnitt betitelt „async und await: asynchron schreiben wie synchron“Promise-Ketten sind Callbacks weit überlegen, aber immer noch eine eigene Denkform. Die Schlüsselwörter async und await machen daraus Code, der wie gewöhnlicher, zeilenweiser Code aussieht:
async function printInvoiceTotal(userId: string): Promise<void> { try { const user = await loadUser(userId); const orders = await loadOrders(user); const firstOrder = orders[0]; // Order | undefined (noUncheckedIndexedAccess) if (!firstOrder) throw new Error(`user ${userId} has no orders`); const invoice = await loadInvoice(firstOrder); console.log(invoice.total); } catch (error) { console.error("failed:", error); } finally { console.log("done either way"); }}Beispielprojekt: 03_async_javascript_examples/ex06_async_await
Die Zeilen mit firstOrder kennst du aus Kapitel 1: Unter noUncheckedIndexedAccess hat orders[0] den Typ Order | undefined, weil die Liste leer sein könnte. Der Fehler, der im try geworfen wird, landet im selben catch wie ein gescheitertes Promise.
Die Regeln
Abschnitt betitelt „Die Regeln“awaitdarf nur in Funktionen stehen, die mitasyncmarkiert sind, und auf oberster Ebene eines Moduls.await promisepausiert diese Funktion, bis das Promise erfüllt ist, und liefert dessen Wert. Pausiert wird nur die Funktion, nicht das Programm; der Event Loop arbeitet währenddessen anderes ab.- Eine
async-Funktion liefert immer ein Promise, auch wenn sie scheinbar einen normalen Wert zurückgibt (return invoicewird zuPromise<Invoice>). - Ein
rejectedPromise wird beiawaitzu einer geworfenen Exception. Die Fehlerbehandlung ist damit das vertrautetry/catch/finally, mit denselben Strategiefragen wie bei jeder Exception: fangen, wo eine sinnvolle Reaktion möglich ist, sonst weiterreichen.
Genauer erklärt: Im Lokal heißt das, der Kellner merkt sich bei Tisch 1 “weiter, sobald das Schnitzel da ist” und geht inzwischen zu Tisch 2. Kommt während eines await ein Klick oder läuft ein Timer ab, wird dessen Callback sofort bearbeitet. Die Fortsetzung nach dem await kommt als Microtask dran, sobald das Promise erfüllt ist.
Drei Schreibweisen im Vergleich
Abschnitt betitelt „Drei Schreibweisen im Vergleich“async/await ist reine Schreibweise über Promises, kein neues Ausführungsmodell: Hinter jedem await steckt ein then. Derselbe Ablauf “Benutzer, Bestellungen, Rechnung” sieht in den drei Formen aus Einheit 3.1 und dieser Einheit so aus:
| Callbacks | Promise-Kette | async/await | |
|---|---|---|---|
| Form | Treppe, jede Stufe eingerückt | flache Kette mit then | Zeile für Zeile |
| Fehlerbehandlung | auf jeder Stufe einzeln | ein catch am Ende | try/catch/finally |
| Zwischenwerte | nur in der jeweiligen Stufe sichtbar | nur im jeweiligen then | normale Variablen |
| Heute eingesetzt für | Ereignisse wie Klicks und Timer | kurze Einzeiler | den Normalfall |
Wer den Event Loop verstanden hat, kann alle drei Formen lesen. Im eigenen Code ist async/await der Standard, then-Ketten bleiben für kleine Einzeiler.
Wo begegnet dir das? In Next.js ab Kapitel 9: Eine Seite, die Daten vom Server braucht, ist dort eine async-Funktion, die ihre Daten mit await lädt, bevor sie die Seite erzeugt. Auch jedes Skript in Node.js, das Dateien liest oder Daten abruft, schreibst du ab jetzt so.
Das folgende Video (freiwillig, auf Englisch) zeigt die Abarbeitung von Promises Schritt für Schritt animiert und ergänzt diesen Abschnitt und die Warteschlangen aus Einheit 3.1 um eine visuelle Darstellung von Microtasks, Task-Queue und async/await: JavaScript Visualized - Promise Execution von Lydia Hallie.
Auflösung
Node.js gibt Promise { 42 } aus: Eine async-Funktion liefert immer ein Promise, auch bei return 42. An die Zahl kommst du mit console.log(await getAnswer()); in einer async-Funktion bzw. auf oberster Ebene eines Moduls, oder mit getAnswer().then((n) => console.log(n));.
Die typischen Fallen
Abschnitt betitelt „Die typischen Fallen“Drei Fehler verursachen den Großteil aller Async-Bugs. Alle drei sind mit TypeScript, einem Linter und etwas Aufmerksamkeit vermeidbar. Die ersten beiden zeigt dieser Abschnitt, der dritte folgt in Einheit 3.3.
Das vergessene await
Abschnitt betitelt „Das vergessene await“async function loadCount(): Promise<number> { /* ... */ }
const count = loadCount(); // forgot awaitconsole.log(count + 1); // "[object Promise]1" nonsenseBeispielprojekt: 03_async_javascript_examples/ex07_forgotten_await
Ohne await landet das Promise selbst in der Variablen, nicht sein Wert. TypeScript macht daraus hier einen Compile-Fehler (Operator '+' cannot be applied to types 'Promise<number>' and '1'), einer der spürbarsten Vorteile der Typisierung in diesem Kapitel. Wie bei jedem Typfehler gilt aber: tsx startet das Programm trotzdem, erst npm run typecheck meldet ihn.
Das Floating Promise
Abschnitt betitelt „Das Floating Promise“Verwandt ist das Floating Promise: eine aufgerufene async-Funktion, deren Ergebnis niemand beachtet. Scheitert sie, fängt niemand den Fehler. Was dann passiert, hängt von der Umgebung ab:
- Node.js (ab Version 15) beendet das Programm mit einer Unhandled Rejection, aber erst in dem Moment, in dem der Fehler auftritt. Das ist womöglich lange nach dem Aufruf und nachdem das Programm schon “fertig” gemeldet hat. Die Fehlermeldung zeigt nicht, wo das
awaitvergessen wurde. - Im Browser erscheint nur eine Meldung in der Konsole der DevTools. Die Seite läuft weiter, als wäre nichts gewesen.
- In Frameworks und Servern, die einen globalen Handler für unbehandelte Fehler installieren, verschwindet der Fehler oft ganz.
In allen drei Fällen ist die eigentliche Aufgabe nicht erledigt worden, und niemand hat es an der richtigen Stelle bemerkt.
Biome findet Floating Promises
Abschnitt betitelt „Biome findet Floating Promises“Gegen dieses Fehlerbild hilft ein Linter, und zwar Biome, das du aus Einheit 1.1 kennst (Abschnitt „Biome: prüfen und formatieren mit einem Werkzeug”). Die passende Regel heißt noFloatingPromises. Sie ist allerdings noch eine Nursery-Regel, also eine Regel in Erprobung, und gehört nicht zum empfohlenen Regelsatz. Mit der biome.json, die biome init anlegt, meldet Biome ein Floating Promise deshalb gar nicht. Du musst die Regel ausdrücklich einschalten:
{ "linter": { "enabled": true, "rules": { "preset": "recommended", "nursery": { "noFloatingPromises": "error" } } }}Dann meldet biome check jeden Aufruf einer async-Funktion, dessen Promise niemand behandelt: in einer anderen async-Funktion, als main(); auf oberster Ebene, bei Funktionen aus einem anderen Modul und auch in reinen .js-Dateien. Als behandelt gilt ein Promise, das du mit await erwartest, das mit .catch(...) einen Fehlerpfad bekommt oder das du mit void ausdrücklich als „bewusst ignoriert” markierst.
Genauer erklärt: Aus saveReport(); wird je nach Absicht await saveReport();, saveReport().catch(logError); oder void saveReport();, und die Entscheidung steht im Code, statt verloren zu gehen. Biome bietet als Korrektur „await ergänzen” an, aber nur als unsafe fix, den du selbst bestätigst, weil ein zusätzliches await den Ablauf verändert.
Wo begegnet dir das? Ein Formular zeigt nach dem Klick auf “Speichern” sofort “gespeichert”, weil der Code das Promise des Speicherns nie erwartet hat. Scheitert die Verbindung zum Server, steht nur eine rote Zeile in der Konsole, die niemand liest, und die Eingaben sind weg. Genau das zeigt Abb. 3.8 am Anfang dieser Einheit.
Auflösung
(a) await saveReport();, am besten in einem try/catch, damit ein Fehler statt „gespeichert“ gemeldet wird. (b) sendStatistics().catch(logError);: Der Ablauf wartet nicht, der Fehler hat trotzdem einen Pfad. (c) void prefetchImages();: bewusst ignoriert. Das ist nur sicher, weil die Funktion ihre Fehler selbst fängt; void allein fängt keinen Fehler ab. Biome akzeptiert alle drei Formen.
Stolperfallen
Abschnitt betitelt „Stolperfallen“- “
awaithält das Programm an”: Pausiert wird nur die eigeneasync-Funktion. Der Event Loop arbeitet währenddessen Klicks, Timer und andere Callbacks ab. - Den Wert einer
async-Funktion direkt verwenden:console.log(getAnswer())zeigtPromise { 42 }, nicht42. An den Wert kommst du mitawaitoderthen. - Vergessenes
await:[object Promise]in der Ausgabe oder eine Dauer von fast 0 ms bei einer eigentlich langsamen Operation. try/catchohneawait: Dertry-Block ist schon verlassen, wenn das Promise später scheitert; dascatchläuft nie, und Node.js beendet das Programm mit einer Unhandled Rejection..catch()ohne Rückgabewert: Die Kette läuft mitundefinedweiter. Imcatcheinen Ersatzwert liefern oder den Fehler weiterwerfen.- Biome meldet nichts, also gibt es keine Floating Promises:
noFloatingPromisesist nicht im empfohlenen Regelsatz. Ohne dennursery-Eintrag in derbiome.jsonbleibt jedes vergessene Promise still. voidals Fehlerbehandlung missverstanden:voidmarkiert ein Promise nur als bewusst ignoriert. Scheitert es, beendet Node.js das Programm trotzdem mit einer Unhandled Rejection.voidpasst nur, wenn die Funktion ihre Fehler selbst fängt.- Programm läuft, also passt das
await:tsxund der Vite-Dev-Server prüfen keine Typen. Den Compile-Fehler beim vergessenenawaitzeigt erstnpm run typecheck.
Zusatzmaterialien
Abschnitt betitelt „Zusatzmaterialien“- javascript.info: Async/await -
async/awaitmit Aufgaben und Lösungen. - MDN: async function - die genauen Regeln für
asyncundawait, inklusive Rückgabewert und Fehlerverhalten. - Biome: noFloatingPromises - wie ein Linter vergessene Promises findet und wie du die Regel einschaltest; Grundlage für Aufgabe 03-3a, Stufe MandalorianPlus.
- Video JavaScript Visualized - Promise Execution von Lydia Hallie (Englisch) - Microtasks, Task-Queue und
async/awaitanimiert.
Zum Anhören
Abschnitt betitelt „Zum Anhören“Du kannst dir diese Einheit kostenlos mit der Vorlesefunktion deines Browsers oder Betriebssystems vorlesen lassen (etwa „Laut vorlesen” in Edge), ohne Konto und ohne Upload; die Codebeispiele liest du dabei am Bildschirm mit. Freiwillig kannst du mit Google NotebookLM (Google-Konto nötig) aus diesem öffentlichen Text eine gesprochene Zusammenfassung erzeugen, auch auf Deutsch.
Lernergebnisse
Abschnitt betitelt „Lernergebnisse“Du kannst jetzt:
- asynchrone Abläufe mit
async/awaitschreiben, mittry/catch/finallyabsichern und in TypeScript mitPromise<T>typisieren. - die Ausgabereihenfolge von Code mit
awaitvorhersagen und Callbacks, Promise-Kette undasync/awaitvergleichen. - ein vergessenes
awaitund ein Floating Promise an ihren Symptomen erkennen und mitawait,.catch(...)odervoidsowie der Biome-RegelnoFloatingPromisesbeheben.
Passende Übungen
Abschnitt betitelt „Passende Übungen“- Aufgabe 03-1a - Reihenfolge-Rätsel, Stufe Wesentlich_vollständig (Rätsel mit
async/await) - Aufgabe 03-2a - Promise-Werkstatt, Stufen Wesentlich_überwiegend und Wesentlich_vollständig
Quellen
Abschnitt betitelt „Quellen“- Regeln für
asyncundawait(Rückgabewert, geworfene Exception) → MDN: async function und await noUncheckedIndexedAccess(TypT | undefinedbei Array-Zugriff) → TSConfig-Referenz: noUncheckedIndexedAccess; Compiler-Meldung beim vergessenenawait→ eigener Test mit TypeScript 7.0.2 (Oktober 2026)- Node.js beendet das Programm bei einer Unhandled Rejection (ab Version 15) → Node.js-Blog: Node v15.0.0 und Node.js-Dokumentation: Event ‘unhandledRejection’; Verhalten mit Node.js 24.11.1 selbst getestet, auch dass
voideine Ablehnung nicht abfängt - Browser meldet unbehandelte Ablehnungen nur in der Konsole → MDN: Window: unhandledrejection event
- Biome-Regel
noFloatingPromises(Nursery, Einschalten inbiome.json, akzeptierte Schreibweisen, unsafe fix) → Biome: noFloatingPromises; Verhalten mit Biome 2.5.15 und Node.js 24.11.1 selbst getestet - Biome allgemein,
biome init,biome check→ Biome: Getting Started - Ausgabe
Promise { 42 }→ eigener Test mit Node.js 24.11.1 (Oktober 2026) - Video zur Abarbeitung von Promises → „JavaScript Visualized - Promise Execution”, Lydia Hallie, eingebettet, nicht kopiert
- Abb. 3.8 (Speichern ohne
await, Mermaid), Abb. 3.9 (awaitpausiert die Funktion), Abb. 3.10 (Floating Promise) → eigene Darstellung
Autoren
Abschnitt betitelt „Autoren“- Oliver Maurhart (MAO)
- Michael Gabriel (GAM)
CC-BY-SA