Kodokon kodokon.com

Asynchronität in der Tiefe: Promises, async/await, Fehlerbehandlung

Nutze die Event-Loop, parallelisiere deine Promises richtig und mache die asynchrone Fehlerbehandlung zuverlässig.

11 Min. · 3 Fragen

Diese Lektion in Kodokon öffnen

Das Ausführungsmodell stützt sich auf eine Makrotask-Warteschlange (setTimeout, I/O) und eine Mikrotask-Warteschlange (.then, await, queueMicrotask). Nach jeder Task leert die Engine die Mikrotask-Warteschlange vollständig, bevor sie weitermacht. Direkte Folge: Eine aufgelöste Promise läuft immer vor einem setTimeout(fn, 0), und eine zu lange Kette von Mikrotasks kann das Rendering genauso sicher einfrieren wie eine synchrone Schleife.

JAVASCRIPT
console.log("start");
setTimeout(() => console.log("macrotask"), 0);
Promise.resolve().then(() => console.log("microtask"));
console.log("end");
// start, end, microtask, macrotask
Mikrotasks laufen vor jeder ausstehenden Makrotask.

async/await ist Zucker auf Promises, doch versteckt es eine Performance-Falle: Jedes await innerhalb einer Schleife serialisiert die Operationen. Wenn die Anfragen unabhängig sind, starte sie zuerst alle und warte dann mit Promise.all auf den Stapel. Bei drei Anfragen von je 100 ms kostet die sequenzielle Version 300 ms, die parallele 100 ms.

JAVASCRIPT
const fetchUser = (id) => new Promise((res) => {
  setTimeout(() => res({ id }), 100);
});
async function loadSequential(ids) {
  const users = [];
  for (const id of ids) {
    users.push(await fetchUser(id)); // ~100 ms each
  }
  return users;
}
async function loadParallel(ids) {
  return Promise.all(ids.map(fetchUser)); // ~100 ms total
}
Gleiches Ergebnis, Latenz geteilt durch die Anzahl der Aufrufe.

Wähle den richtigen Kombinator für den Fehlervertrag, den du brauchst: Promise.all lehnt beim ersten Fehler ab (alles oder nichts), allSettled wartet auf alle und beschreibt jedes Ergebnis, race nimmt die erste Beilegung (Erfolg oder Fehler, nützlich für einen Timeout), any nimmt den ersten Erfolg und lehnt nur ab, wenn alles fehlschlägt. Für ein Dashboard, das Teilergebnisse zeigen muss, ist allSettled fast immer die richtige Wahl.

JAVASCRIPT
const tasks = [
  Promise.resolve("profile loaded"),
  Promise.reject(new Error("network unavailable")),
];
const results = await Promise.allSettled(tasks);
for (const r of results) {
  if (r.status === "fulfilled") {
    console.log("success:", r.value);
  } else {
    console.error("failure:", r.reason.message);
  }
}
Jedes Ergebnis wird beschrieben; kein Fehler verdeckt die anderen.

Wissenscheck

Stelle sicher, dass du die wichtigsten Punkte dieser Lektion behalten hast.

  1. In welcher Reihenfolge laufen ein synchrones console.log, ein Promise.resolve().then-Callback und ein setTimeout(fn, 0)-Callback?
    • synchron, setTimeout, then
    • synchron, then, setTimeout
    • then, synchron, setTimeout
  2. Du löst zehn unabhängige Anfragen aus. Welcher Ansatz minimiert die Gesamtlatenz?
    • Ein await pro Anfrage innerhalb einer for...of-Schleife
    • Alle Promises erzeugen, dann Promise.all abwarten
    • Die .then-Aufrufe nacheinander verketten
    • setTimeout verwenden, um die Aufrufe zu staffeln
  3. Warum fängt try { doAsync(); } catch (e) {} ohne await die Ablehnung von doAsync nicht ab?
    • Weil catch innerhalb von async-Funktionen nicht funktioniert
    • Weil die Ablehnung geschieht, nachdem der try-Block verlassen wurde, da auf die Promise nie gewartet wird
    • Weil Promises nur mit .catch abgefangen werden können