Passer au contenu principal

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 :

    • Callbacks des Promesses appelés dans .then, .catch et .finally

    • Callbacks de  queueMicrotask()

    • Callbacks de new MutationObserver()

    • Fonctions exécutées après un await

Les composants de l'environnement d'exécution de JavaScript.png

 

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.