async/await in JavaScript: Asynchronen Code lesbar schreiben
Promises sind mächtig, aber verschachtelte .then()-Ketten werden schnell unübersichtlich. Mit async und await schreibst du asynchronen Code, der sich liest wie synchroner – und behandelst Fehler ganz normal mit try/catch.
Wenn du schon mit Promises gearbeitet hast, kennst du das Gefühl: Der Code funktioniert, aber bei mehreren aufeinanderfolgenden Schritten wächst eine lange Kette aus .then()-Aufrufen, die immer schwerer zu lesen wird. Genau hier setzt async/await an. Es ist keine neue Technik, sondern eine elegantere Schreibweise für Promises – asynchroner Code sieht damit fast so aus wie ganz normaler, sequenzieller Code. In diesem Beitrag schauen wir uns an, wie das funktioniert und worauf du achten musst.
Von Promises zu async/await
Stell dir vor, du willst Nutzerdaten von einer API laden. Mit klassischen Promises sieht das so aus:
function ladeNutzer(id) {
return fetch(`https://api.example.com/users/${id}`)
.then((response) => response.json())
.then((nutzer) => {
console.log(nutzer.name);
return nutzer;
});
}Das ist noch überschaubar. Mit async/await wird daraus aber etwas, das sich fast wie eine normale Anweisung nach der anderen liest:
async function ladeNutzer(id) {
const response = await fetch(`https://api.example.com/users/${id}`);
const nutzer = await response.json();
console.log(nutzer.name);
return nutzer;
}Zwei Schlüsselwörter machen das möglich: async vor der Funktion und await vor jedem Promise. Das Wort await bedeutet „warte, bis dieses Promise fertig ist, und gib mir den Wert“. Wichtig: Der Rest deines Programms wird dabei nicht blockiert – JavaScript arbeitet an anderen Aufgaben weiter, während gewartet wird.
Wie async und await zusammenspielen
Es gibt zwei feste Regeln, die du dir merken solltest:
awaitdarf nur innerhalb einerasync-Funktion verwendet werden (oder auf oberster Ebene eines ES-Moduls).- Eine
async-Funktion gibt immer ein Promise zurück, egal was du in ihr zurückgibst.
Das zweite Detail überrascht viele Einsteiger. Selbst wenn du eine einfache Zahl zurückgibst, ist das Ergebnis ein Promise:
async function gibZahl() {
return 42;
}
// Das Ergebnis ist ein Promise, kein direkter Wert:
const ergebnis = gibZahl();
console.log(ergebnis); // Promise { 42 }
// Deshalb musst du es entweder await-en ...
const zahl = await gibZahl();
console.log(zahl); // 42
// ... oder mit .then() darauf reagieren:
gibZahl().then((wert) => console.log(wert)); // 42Das bedeutet auch: async/await und .then() sind kein Widerspruch. Unter der Haube ist alles ein Promise – du wählst nur die Schreibweise, die an der jeweiligen Stelle besser lesbar ist.
Fehler sauber behandeln mit try/catch
Einer der größten Vorteile von async/await ist die Fehlerbehandlung. Bei Promises brauchst du dafür .catch(). Mit await nutzt du stattdessen das ganz normale try/catch, das du vielleicht schon aus synchronem Code kennst:
async function ladeNutzerSicher(id) {
try {
const response = await fetch(`https://api.example.com/users/${id}`);
if (!response.ok) {
throw new Error(`HTTP-Fehler: ${response.status}`);
}
const nutzer = await response.json();
return nutzer;
} catch (fehler) {
console.error("Konnte Nutzer nicht laden:", fehler.message);
return null;
}
}Beachte den Check if (!response.ok): fetch wirft nämlich keinen Fehler bei HTTP-Statuscodes wie 404 oder 500 – nur bei Netzwerkproblemen. Deshalb prüfst du den Status selbst und wirfst bei Bedarf einen eigenen Fehler, der dann im catch-Block landet.
Der häufigste Fehler: unnötiges Warten
Ein Stolperstein, in den fast jeder einmal tappt: mehrere unabhängige Aufgaben nacheinander abwarten, obwohl sie gleichzeitig laufen könnten. Schau dir diesen langsamen Code an:
// Langsam: wartet erst auf A, dann auf B, dann auf C
async function ladeAllesLangsam() {
const a = await ladeNutzer(1); // z. B. 1 Sekunde
const b = await ladeNutzer(2); // noch 1 Sekunde
const c = await ladeNutzer(3); // noch 1 Sekunde
return [a, b, c]; // insgesamt ca. 3 Sekunden
}Da die drei Abfragen nichts voneinander wissen müssen, kannst du sie parallel starten und gemeinsam abwarten. Dafür gibt es Promise.all:
// Schnell: alle drei starten gleichzeitig
async function ladeAllesSchnell() {
const [a, b, c] = await Promise.all([
ladeNutzer(1),
ladeNutzer(2),
ladeNutzer(3),
]);
return [a, b, c]; // insgesamt ca. 1 Sekunde
}Die Faustregel: Nutze await in einer Schleife oder direkt hintereinander nur dann, wenn ein Schritt wirklich das Ergebnis des vorherigen braucht. Sind die Aufgaben unabhängig, gehört Promise.all an ihren Platz.
async/await mit Schleifen
Willst du eine Liste sequenziell abarbeiten – etwa weil jeder Schritt auf dem vorherigen aufbaut –, funktioniert await in einer normalen for-Schleife problemlos:
async function verarbeiteReihenfolge(ids) {
const ergebnisse = [];
for (const id of ids) {
const nutzer = await ladeNutzer(id);
ergebnisse.push(nutzer);
}
return ergebnisse;
}Ein wichtiger Hinweis: await innerhalb von .forEach() funktioniert nicht wie erwartet, weil forEach nicht auf die Promises wartet. Nutze stattdessen eine for...of-Schleife (für sequenzielle Verarbeitung) oder Promise.all mit .map() (für parallele).
Fazit
Mit async/await schreibst du asynchronen Code, der sich liest wie synchroner – ohne verschachtelte .then()-Ketten und mit gewohnter try/catch-Fehlerbehandlung. Merke dir die Kernpunkte: await lebt immer in einer async-Funktion, jede async-Funktion gibt ein Promise zurück, und für unabhängige Aufgaben nutzt du Promise.all statt sie einzeln abzuwarten. Wenn du diese Muster verinnerlicht hast, wird asynchroner JavaScript-Code deutlich angenehmer zu schreiben und zu lesen. Am besten baust du dir gleich ein kleines Beispiel mit fetch und einer öffentlichen API – Übung macht hier den entscheidenden Unterschied.