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:
- Alle Microtasks werden zuerst ausgeführt
- Ein Macrotask wird ausgeführt
- 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:
- Synchrones
console.log('1')
wird sofort ausgeführt setTimeout
plant Callback in die Macrotask-Warteschlange ein- Promise-Callbacks werden als Microtasks in die Warteschlange eingereiht
- Synchrones
console.log('5')
wird ausgeführt - Event Loop verarbeitet alle Microtasks (3, 4)
- 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.
Discover how at OpenReplay.com.
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.