Abschnitt 15 · Lektion 5
async und await
Promises haben die Verschachtelung beseitigt. Was sie nicht beseitigt haben, ist die Tatsache, dass dein Code anders aussieht, sobald etwas Zeit braucht: überall then, überall Funktionen in Funktionen.
async und await räumen auch das weg. Sie sind kein neues Konzept, sondern eine andere Schreibweise für genau dasselbe. Darunter liegen weiterhin Promises.
await
<p>Oben mit then, darunter mit await. Dasselbe Ergebnis.</p> body {
font-family: system-ui, sans-serif;
max-width: 40rem;
margin: 0;
padding: 1rem;
line-height: 1.6;
color: #1c1917;
}
p {
margin: 0 0 0.75rem;
} function ladeName(id) {
return new Promise((aufloesen) => {
setTimeout(() => aufloesen(`Person ${id}`), 100);
});
}
// Mit then.
function mitThen() {
return ladeName(1)
.then((name) => name.toUpperCase())
.then((gross) => `${gross} hat ${gross.length} Zeichen`);
}
// Mit await. Liest sich wie gewöhnlicher Code.
async function mitAwait() {
const name = await ladeName(1);
const gross = name.toUpperCase();
return `${gross} hat ${gross.length} Zeichen`;
}
mitThen().then((text) => console.log("then:", text));
mitAwait().then((text) => console.log("await:", text)); await steht vor einem Promise und liefert seinen Wert.
Das ist die ganze Idee. Aus
ladeName(1).then((name) => ...)
wird
const name = await ladeName(1);
und ab der nächsten Zeile arbeitest du mit dem Namen, als wäre er schon immer dagewesen.
Im ersten Beispiel steht dieselbe Kette zweimal. Die await-Fassung liest sich von oben nach unten, ohne Rückwärtssprünge und ohne eine einzige zusätzliche Funktion.
Blockiert wird dabei nichts. await sieht aus wie Warten und ist keines: Die Funktion gibt die Kontrolle ab, alles andere läuft weiter, und wenn das Promise soweit ist, macht sie an dieser Stelle weiter. Das Bild aus Lektion 15.2 gilt unverändert, nur dass du es nicht mehr hinschreiben musst.
async
await darf nur in einer Funktion stehen, die mit async gekennzeichnet ist. Ohne das ist es ein SyntaxError.
Und async hat eine zweite Wirkung, die genauso wichtig ist: Eine async-Funktion gibt immer ein Promise zurück. Auch wenn im return nur eine schlichte Zahl steht.
Das klingt nach einer Formalität und ist der Grund für die häufigste Verwirrung in diesem Thema:
Eine async-Funktion ist für ihren Aufrufer asynchron. Du hast das await in ihr Inneres verlegt, nicht abgeschafft. Wer sie aufruft, bekommt wieder ein Promise und braucht selbst ein await oder ein then.
Es gibt eine Ausnahme davon, und die kommt in Lektion 18.4: In Modulen darf await auch außerhalb einer Funktion stehen, ganz oben. Das heißt Top-Level-Await und funktioniert nur dort.
try/catch funktioniert wieder
<p>Der Fehler kommt aus einem Timer und wird trotzdem gefangen.</p> body {
font-family: system-ui, sans-serif;
max-width: 40rem;
margin: 0;
padding: 1rem;
line-height: 1.6;
color: #1c1917;
}
p {
margin: 0 0 0.75rem;
} function ladenDasScheitert() {
return new Promise((aufloesen, ablehnen) => {
setTimeout(() => ablehnen(new Error("Verbindung abgebrochen")), 100);
});
}
async function versuchen() {
try {
const wert = await ladenDasScheitert();
console.log("Bekommen:", wert);
return wert;
} catch (fehler) {
console.warn("Gefangen wie ein gewöhnlicher Fehler:", fehler.message);
return "Ersatzwert";
} finally {
console.log("Und finally läuft auch hier.");
}
}
versuchen().then((ergebnis) => console.log("Ergebnis:", ergebnis)); Das ist der größte Gewinn und der Grund, warum async und await sich so schnell durchgesetzt haben.
In Lektion 15.3 hast du gesehen, dass ein try/catch einen Fehler aus einem Callback nicht fängt: Der try-Block ist längst vorbei. Bei await ist das anders. Die Funktion macht an dieser Stelle weiter, also mitten im try-Block, und ein abgelehntes Promise wird dort zu einem geworfenen Fehler.
Damit hast du wieder ein einziges Werkzeug für alle Fehler: synchrone, asynchrone, eigene, fremde. finally funktioniert ebenfalls.
Wenn du dich zwischen .catch() und try/catch entscheiden musst: In einer async-Funktion nimm try/catch. Es liest sich besser und behandelt beide Arten von Fehlern gleich.
Das vergessene await
<p>Dreimal dasselbe laden, dreimal ein anderes Ergebnis.</p> body {
font-family: system-ui, sans-serif;
max-width: 40rem;
margin: 0;
padding: 1rem;
line-height: 1.6;
color: #1c1917;
}
p {
margin: 0 0 0.75rem;
} async function zahlLaden() {
return 42;
}
async function zeigen() {
// Richtig: await macht aus dem Promise seinen Wert.
const mitAwait = await zahlLaden();
console.log("mit await:", mitAwait, typeof mitAwait);
// Falsch: ohne await bleibt es das Promise selbst.
const ohneAwait = zahlLaden();
console.log("ohne await:", ohneAwait, typeof ohneAwait);
// Und die Rechnung damit geht schief, ohne zu werfen.
console.log("mit await mal 2:", mitAwait * 2);
console.log("ohne await mal 2:", ohneAwait * 2);
}
zeigen(); Der Fehler, den jeder einmal macht, und er wirft nicht.
Ohne await bleibt das Promise ein Promise. Die Variable enthält dann nicht 42, sondern das Objekt, das irgendwann 42 liefern wird. Im dritten Beispiel siehst du beides nebeneinander: typeof ist einmal number und einmal object, und die Rechnung damit ergibt NaN.
Im Reiter Debug brauchst du dafür kein typeof. Zeichne auf, und ab dem zweiten Aufruf steht neben jedem Schritt mitAwait=42, ohneAwait=Promise {}. Die beiden Variablen stehen nebeneinander, und man sieht auf einen Blick, dass in der einen eine Zahl liegt und in der anderen eine Zusage.
Genau so würdest du im Ernstfall danach suchen. Du weißt, dass etwas nicht stimmt, aber nicht wo, und der Reiter zeigt dir alle Variablen auf einmal statt nur der einen, nach der du gerade gefragt hast.
Verräterisch daran ist, dass nichts abbricht. Du bekommst [object Promise] in einer Ausgabe oder NaN in einer Rechnung, und dann suchst du an der falschen Stelle.
Merkhilfe: Wenn irgendwo [object Promise] auftaucht oder eine Rechnung ohne Grund NaN liefert, fehlt ein await.
await in einer Schleife
Zum Schluss eine Falle, die kein Fehler ist, aber teuer wird.
Wenn du in einer Schleife await benutzt, wartet jeder Durchlauf auf den vorigen. Zehn Anfragen zu je 200 Millisekunden brauchen dann zwei Sekunden statt 200 Millisekunden.
Manchmal ist das genau richtig, nämlich wenn ein Schritt das Ergebnis des vorigen braucht. Oft ist es das nicht: Die zehn Anfragen sind voneinander unabhängig und könnten alle gleichzeitig laufen.
Wie man das schreibt, ist die nächste Lektion.
Zum Mitnehmen
await macht aus einem Promise seinen Wert. async macht aus einem Rückgabewert ein Promise. Und try/catch funktioniert wieder.
Jetzt du
Basis Konto, kostenlosZu dieser Lektion gehört eine Aufgabe. Du schreibst den Code selbst, und nach jedem Lauf sagt dir eine Prüfliste, was schon stimmt.
Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.
Was in dieser Lektion steckt
-
Artikel mit 3 Beispielen zum Ausprobieren
Steht hier, ohne Konto lesbar.
-
Aufgabe im Editor, direkt im Browser geprüft
Öffnet sich mit dem Basis Konto.