Back

Wie JavaScript Promises mit der Event Loop funktionieren

Wie JavaScript Promises mit der Event Loop funktionieren

Das asynchrone Verhalten von JavaScript verwirrt oft Entwickler. Sie schreiben setTimeout(() => console.log('timeout'), 0) und erwarten eine sofortige Ausführung, doch ein Promise wird zuerst aufgelöst. Das Verständnis, wie JavaScript Promises mit der Event Loop interagieren, erklärt, warum dies geschieht, und hilft Ihnen, vorhersehbareren asynchronen Code zu schreiben.

Wichtigste Erkenntnisse

  • Die Event Loop verarbeitet alle Microtasks, bevor sie zum nächsten Macrotask übergeht
  • Promises und async/await nutzen die Microtask-Warteschlange und erhalten Vorrang vor setTimeout
  • Das Verständnis des Zwei-Warteschlangen-Systems hilft, die Ausführungsreihenfolge von JavaScript vorherzusagen
  • Rekursive Microtasks können die Event Loop aushungern und Macrotasks blockieren

Die Grundlagen der JavaScript Event Loop

JavaScript läuft auf einem einzelnen Thread und verarbeitet jeweils eine Operation. Die Event Loop ermöglicht asynchrone Operationen, indem sie zwischen dem Call Stack und zwei verschiedenen Warteschlangen koordiniert: der Microtask-Warteschlange und der Macrotask-Warteschlange.

Der Call Stack führt synchronen Code sofort aus. Wenn der Stack leer ist, prüft die Event Loop in einer bestimmten Reihenfolge auf ausstehende Aufgaben:

  1. Alle Microtasks werden zuerst ausgeführt
  2. Ein Macrotask wird ausgeführt
  3. Der Zyklus wiederholt sich

Dieses Prioritätssystem erklärt, warum sich Promises anders verhalten als setTimeout.

JavaScript Macrotask vs. Microtask: Der entscheidende Unterschied

Das Verständnis der Unterschiede zwischen Macrotask und Microtask ist entscheidend für die Vorhersage der Code-Ausführungsreihenfolge.

Zu den Macrotasks gehören:

  • setTimeout
  • setInterval
  • I/O-Operationen
  • UI-Rendering

Zu den Microtasks gehören:

  • Promise-Callbacks (.then, .catch, .finally)
  • queueMicrotask()
  • MutationObserver-Callbacks

Die Event Loop verarbeitet alle Microtasks, bevor sie zum nächsten Macrotask übergeht. Dies schafft ein Prioritätssystem, bei dem Promises immer vor Timern ausgeführt werden.

Promises und die Microtask-Warteschlange in Aktion

Betrachten wir anhand von Code, wie Promises mit der Event Loop interagieren:

console.log('1');

setTimeout(() => console.log('2'), 0);

Promise.resolve()
  .then(() => console.log('3'))
  .then(() => console.log('4'));

console.log('5');

Ausgabe: 1, 5, 3, 4, 2

Hier ist der Ausführungsablauf:

  1. Synchrones console.log('1') wird sofort ausgeführt
  2. setTimeout plant Callback in die Macrotask-Warteschlange ein
  3. Promise-Callbacks werden als Microtasks in die Warteschlange eingereiht
  4. Synchrones console.log('5') wird ausgeführt
  5. Event Loop verarbeitet alle Microtasks (3, 4)
  6. Event Loop verarbeitet einen Macrotask (2)

Die Microtask-Warteschlange wird vollständig geleert, bevor ein Macrotask ausgeführt wird, selbst bei Timeouts mit Nullverzögerung.

Async/Await und die Integration in die Event Loop

Das Verhalten von Async/Await folgt denselben Microtask-Regeln. Das Schlüsselwort await pausiert die Funktionsausführung und plant die Fortsetzung als Microtask ein:

async function example() {
  console.log('1');
  await Promise.resolve();
  console.log('2');  // Dies wird zu einem Microtask
}

example();
console.log('3');

// Ausgabe: 1, 3, 2

Nach await wird der verbleibende Funktionskörper der Microtask-Warteschlange hinzugefügt. Dies erklärt, warum console.log('3') vor console.log('2') ausgeführt wird, obwohl es später im Code erscheint.

Häufige Fallstricke und praktische Muster

Aushungern der Microtask-Warteschlange

Das rekursive Erstellen von Microtasks kann die Event Loop blockieren:

function dangerousLoop() {
  Promise.resolve().then(dangerousLoop);
}
// Tun Sie dies nicht - blockiert alle Macrotasks

Kombination von Timern mit Promises

Wenn Sie verschiedene asynchrone Muster kombinieren, denken Sie an die Ausführungspriorität:

setTimeout(() => console.log('timeout'), 0);

fetch('/api/data')
  .then(response => response.json())
  .then(data => console.log('data'));

Promise.resolve().then(() => console.log('immediate'));

// Reihenfolge: immediate → data (wenn bereit) → timeout

Debugging der Ausführungsreihenfolge

Verwenden Sie dieses Muster, um den Ausführungsablauf zu verfolgen:

console.log('Sync start');

queueMicrotask(() => console.log('Microtask 1'));

setTimeout(() => console.log('Macrotask 1'), 0);

Promise.resolve()
  .then(() => console.log('Microtask 2'))
  .then(() => console.log('Microtask 3'));

setTimeout(() => console.log('Macrotask 2'), 0);

console.log('Sync end');

// Ausgabe: Sync start, Sync end, Microtask 1, Microtask 2, Microtask 3, Macrotask 1, Macrotask 2

Fazit

Das Zwei-Warteschlangen-System der Event Loop bestimmt die asynchrone Ausführungsreihenfolge von JavaScript. Promises und async/await nutzen die Microtask-Warteschlange und erhalten Vorrang vor setTimeout und anderen Macrotasks. Dieses Wissen verwandelt mysteriöses asynchrones Verhalten in vorhersehbare Muster und ermöglicht es Ihnen, zuverlässigeren asynchronen JavaScript-Code zu schreiben.

Merken Sie sich: Synchroner Code wird zuerst ausgeführt, dann werden alle Microtasks abgearbeitet, dann wird ein Macrotask ausgeführt. Dieser Zyklus wiederholt sich und ermöglicht es dem einzelnen Thread von JavaScript, komplexe asynchrone Operationen effizient zu verarbeiten.

Häufig gestellte Fragen (FAQs)

Promise-Callbacks sind Microtasks, während setTimeout Macrotasks erstellt. Die Event Loop verarbeitet immer alle Microtasks, bevor sie zum nächsten Macrotask übergeht, unabhängig von der Timeout-Dauer.

Ja, das kontinuierliche Erstellen von Promises oder Microtasks ohne Ausführung von Macrotasks kann die Event Loop aushungern. Dies verhindert, dass UI-Updates und andere Macrotasks ausgeführt werden.

Async/await ist syntaktischer Zucker für Promises. Das Schlüsselwort await pausiert die Ausführung und plant die Fortsetzung als Microtask ein, wobei dieselben Prioritätsregeln wie bei Promise-Callbacks befolgt werden.

Verschiedene Muster folgen ihren Warteschlangenregeln. Promises und async/await verwenden Microtasks, während setTimeout und setInterval Macrotasks verwenden. Microtasks werden immer zuerst ausgeführt, wenn der Call Stack leer ist.

Complete picture for complete understanding

Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.

Check our GitHub repo and join the thousands of developers in our community.

OpenReplay