Initialisation des systèmes...

Baptiste.Dev
Retour aux notes
LangagesAvancéSérie : JavaScript

JavaScript : L'event loop

Comment un langage monothread gère l'asynchrone sans se bloquer : call stack, API du navigateur, micro et macro-tasks, et la règle d'or de leur priorité.

Par Baptiste Vidal
2 min de lecture
Mis à jour il y a 1 mois
javascriptjsevent-loopasynchronemicrotask

JavaScript : L'event loop

On a vu que JavaScript est monothread et asynchrone. Mais comment un seul fil d'exécution peut-il gérer des timers, des requêtes réseau et des clics sans jamais se bloquer ? La réponse tient en deux mots : l'event loop. C'est le mécanisme le plus mal compris du langage, et le saisir change ta façon de lire du code asynchrone.


# Un seul thread, plusieurs acteurs

Le moteur JavaScript est bien monothread : il n'a qu'une seule call stack (pile d'exécution) et n'y fait qu'une chose à la fois. Mais il ne travaille pas seul. Le navigateur (ou Node.js) l'entoure de plusieurs pièces :

  • La call stack : là où ton code s'exécute, une instruction après l'autre.
  • Les API de l'environnement : setTimeout, fetch, les événements du DOM. Elles tournent en dehors du thread JavaScript.
  • La macrotask queue : la file des callbacks « classiques » prêts (un setTimeout échu, un événement).
  • La microtask queue : la file des callbacks de promesses (.then, le code après un await).
  • L'event loop : le chef d'orchestre qui relie tout ça.

# Comment ça tourne

Le fonctionnement de l'event loop tient en quelques étapes, répétées en boucle :

  1. Exécuter tout le code synchrone de la call stack, jusqu'à ce qu'elle soit vide.
  2. Une fois la stack vide, vider entièrement la microtask queue (toutes les promesses en attente).
  3. Prendre une seule macrotask (un setTimeout par exemple), l'exécuter.
  4. Re-vider toutes les microtasks accumulées entre-temps.
  5. Recommencer.

La règle d'or à retenir : les microtasks (promesses) sont toujours traitées avant les macrotasks (timers).


# L'exemple qui clarifie tout

console.log("1");
 
setTimeout(() => console.log("2"), 0);
 
Promise.resolve().then(() => console.log("3"));
 
console.log("4");

L'ordre d'affichage est 1, 4, 3, 2. Pourquoi ?

  • 1 et 4 sont synchrones : exécutés immédiatement, dans l'ordre.
  • 3 est une microtask (une promesse) : vidée dès que la stack est libre, donc avant les timers.
  • 2 est une macrotask (setTimeout) : traitée en dernier, même avec un délai de 0.

Autrement dit, setTimeout(fn, 0) ne veut pas dire « maintenant », mais « dès que possible, une fois la pile vide et les promesses traitées ».


# Pourquoi ça compte

  • Comprendre pourquoi un setTimeout(fn, 0) n'est jamais instantané.
  • Savoir qu'une boucle synchrone lourde bloque tout, y compris l'interface : rien d'autre ne peut s'exécuter tant que la stack n'est pas vide.
  • Anticiper l'ordre réel d'exécution entre plusieurs await et .then.
  • En React, comprendre pourquoi un changement de state n'est pas visible immédiatement après l'appel : la mise à jour est planifiée, pas instantanée.

# À retenir

  • JavaScript est monothread (une seule call stack) ; les opérations asynchrones tournent en dehors du thread.
  • L'event loop récupère les callbacks des files quand la call stack est vide.
  • Microtasks (promesses) avant macrotasks (timers) : la règle qui explique la plupart des surprises.
  • setTimeout(fn, 0) signifie « dès que possible », pas « tout de suite ».