L'Event Loop en JavaScript : Comprendre son fonctionnement
JavaScript est un langage single-threaded, ce qui signifie qu'il ne peut traiter qu'une seule tâche à la fois. Toutefois, grâce à son event loop, il est capable de gérer des opérations asynchrones de manière efficace.
Les composants de l'environnement d'exécution de JavaScript
L’environnement d’exécution de JavaScript repose sur plusieurs éléments clés :
-
Call Stack : Pile d'exécution où sont empilées et dépilées les fonctions en cours d'exécution.
- Event Loop : Mécanisme qui assure l’exécution ordonnée des tâches asynchrones.
-
Web API : Fournie par le navigateur, elle permet d'accéder à des fonctionnalités comme :
-
Fetch API (requêtes réseau)
-
Timers API (setTimeout, setInterval)
-
Console API
-
Geolocation API
-
Web Storage API (sessionStorage et localStorage)
-
File API
-
HTML DOM API
-
URL API
- ...
-
-
Task Queue : File d'attente dédiée aux callbacks issus des Web API et des gestionnaires d'événements.
-
Microtask Queue : File d'attente dédiée aux :
Fonctionnement de l'Event Loop
Lorsqu'un code Javascript est exécuté :
1. Les instructions synchrones sont immédiatement exécutées dans la Call Stack.
2. Les fonctions asynchrones (Web API) comme setTimeout ou fetch sont déléguées au navigateur, qui les exécute en arrière-plan. Une fois terminées, leurs callbacks sont envoyées dans la Task Queue.
3. Les microtasks (promesses, queueMicrotask, MutationObserver, await) sont ajoutées à la Microtask Queue.
4. L’Event Loop surveille en permanence la Call Stack :
- Si la Call Stack est vide, il exécute toutes les tâches de la Microtask Queue avant de passer à la Task Queue.
- Si une nouvelle microtask est ajoutée pendant l’exécution d’une autre, elle est immédiatement traitée avant de passer aux tâches suivantes.
Exemple d'exécution du code Javascript
Prenons l'exemple suivant :
Promise.resolve().then(() => console.log(1));
setTimeout(() => console.log(2), 10);
queueMicrotask(() => {
console.log(3);
queueMicrotask(() => console.log(4));
});
console.log(5);
Étapes d’exécution :
1. La promesse Promise.resolve()
se résout immédiatement, et sa callback () => console.log(1)
est ajoutée à la Microtask Queue.
2. La fonction setTimeout(() => console.log(2), 10)
est envoyée à la Web API, qui gère le délai en arrière-plan. Une fois expiré, sa callback sera placée dans la Task Queue.
3. La callback () => { console.log(3); queueMicrotask(() => console.log(4)); })
sera ajoutée à la Microtask Queue après console.log(1)
.
4. console.log(5)
est exécuté immédiatement car c'est du code synchrone.
5. Exécution des microtasks :
console.log(1)
est exécuté en premier (première tâche dans la Microtask Queue).console.log(3)
s’exécute ensuite, ajoutant() => console.log(4)
à la Microtask Queue.console.log(4)
est immédiatement exécuté, car la Call Stack est vide et que la Microtask Queue a toujours la priorité sur la Task Queue.
6. Une fois la Microtask Queue vide, l’Event Loop vérifie la Task Queue :
- La callback de
() => console.log(2)
a été placée dans la Task Queue une fois le délai écoulé. - L’Event Loop prend alors cette tâche et l’exécute en la plaçant dans la Call Stack, ce qui affiche 2.
Résultat final affiché dans la console : 5 1 3 4 2
📌 À noter : Une tâche (task ou microtask) n’est pas exécutée immédiatement. Elle est d’abord ajoutée à la file d’attente correspondante et programmée pour être exécutée dès que possible.