</> HTML5Advent
ENFRESDEITPT

// js · Web Platform Advent #3

async/await in JavaScript: ein praktischer Leitfaden

Wie async und await funktionieren, wie sie mit Promises zusammenhängen, Fehlerbehandlung mit try/catch, await in Schleifen und der Fallstrick seriell vs. parallel — mit ausführbaren Beispielen.

Die Hände eines Entwicklers tippen auf einer Tastatur, mit buntem Quellcode auf dem Monitor

Die Schlüsselwörter async und await sind Syntax über Promises. Sie erlauben es, asynchronen Code zu schreiben, der sich von oben nach unten wie synchroner Code liest, dabei aber im Hintergrund nicht blockierend bleibt. Wenn Sie Promises kennen, kennen Sie bereits async/await — es nimmt nur die Zeremonie von .then() weg.

Die Grundlagen

Markieren Sie eine Funktion als async, und Sie können darin await verwenden. await pausiert diese Funktion, bis das Promise entschieden ist, und gibt Ihnen dann den aufgelösten Wert:

async function getUser(id) {
  const res = await fetch('/api/user/' + id);
  const user = await res.json();
  return user.name;
}

getUser(42).then((name) => console.log(name));

Zwei Dinge zum Merken: eine async-Funktion gibt immer ein Promise zurück (was immer Sie zurückgeben, wird in eines verpackt), und await funktioniert nur innerhalb einer async-Funktion — außer auf der obersten Ebene eines ES-Moduls, wo Top-Level-await erlaubt ist.

async/await vs. Promises

Es ist dieselbe Maschinerie. Diese Promise-Kette …

fetch('/api/user')
  .then((res) => res.json())
  .then((user) => console.log(user.name));

… ist genau gleichwertig mit dieser async-Version, die die meisten Leute leichter zu lesen und zu debuggen finden:

const res = await fetch('/api/user');
const user = await res.json();
console.log(user.name);

Sie können sie frei mischen — await jedes Promise, einschließlich des Ergebnisses von Promise.all().

Fehlerbehandlung mit try/catch

Mit async/await fangen Sie abgelehnte Promises mit gewöhnlichem try/catch ab, demselben Konstrukt, das Sie für synchrone Fehler verwenden:

async function loadUser(id) {
  try {
    const res = await fetch('/api/user/' + id);
    if (!res.ok) throw new Error('HTTP ' + res.status);
    return await res.json();
  } catch (err) {
    console.error('Could not load user:', err.message);
    return null;
  } finally {
    console.log('loadUser finished');
  }
}

Ein abgelehntes await wirft an dieser Zeile eine Ausnahme, sodass der catch-Block läuft. Der finally-Block läuft, ob ein Fehler aufgetreten ist oder nicht.

Eine mehrspurige Autobahn mit Autos und Lastwagen, die parallel nebeneinander fahren
Unabhängige Aufgaben können nebeneinander laufen wie Fahrzeuge in parallelen Spuren — sie eine nach der anderen abzuwarten erzwingt stattdessen eine Schlange im Gänsemarsch.

Der große Fallstrick: serielles vs. paralleles await

Der häufigste async/await-Fehler ist, unabhängige Aufgaben eine nach der anderen abzuwarten. Das läuft seriell — die zweite Anfrage startet nicht, bevor die erste fertig ist:

// Slow: total time = a + b + c
const a = await fetchA();
const b = await fetchB();
const c = await fetchC();

Wenn die Aufgaben nicht voneinander abhängen, starten Sie sie alle zuerst und warten dann gemeinsam mit Promise.all ab. Jetzt laufen sie parallel:

// Fast: total time ≈ the slowest one
const [a, b, c] = await Promise.all([fetchA(), fetchB(), fetchC()]);

await in Schleifen

Eine einfache for-Schleife mit await verarbeitet Elemente eines nach dem anderen, was korrekt ist, wenn jeder Schritt vom vorherigen abhängt oder Sie einen Server nicht überlasten dürfen:

for (const id of ids) {
  const item = await fetchItem(id);   // sequential — one after another
  console.log(item);
}

Um sie nebenläufig zu verarbeiten, bilden Sie ein Array von Promises ab und nutzen await Promise.all. Beachten Sie, dass forEach nicht auf async-Callbacks wartet — verwenden Sie stattdessen map + Promise.all:

const items = await Promise.all(ids.map((id) => fetchItem(id)));

Schnellreferenz

ZielMuster
Auf ein Promise wartenconst x = await p;
Fehler behandelntry { await ... } catch (e) { ... }
Unabhängige Aufgaben parallel ausführenawait Promise.all([...])
Sequentielle Schleifefor...of mit await
Nebenläufige SchleifemapPromise.all

async/await ersetzt Promises nicht — es macht sie lesbar. Behalten Sie Promise.all für Parallelität in Ihrem Werkzeugkasten, greifen Sie für Fehler zu try/catch und achten Sie auf das versehentliche serielle await, dann haben Sie asynchrones JavaScript gut im Griff.