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é.
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 unawait). - 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 :
- Exécuter tout le code synchrone de la call stack, jusqu'à ce qu'elle soit vide.
- Une fois la stack vide, vider entièrement la microtask queue (toutes les promesses en attente).
- Prendre une seule macrotask (un
setTimeoutpar exemple), l'exécuter. - Re-vider toutes les microtasks accumulées entre-temps.
- 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");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 ?
1et4sont synchrones : exécutés immédiatement, dans l'ordre.3est une microtask (une promesse) : vidée dès que la stack est libre, donc avant les timers.2est 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
awaitet.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 ».