Kodokon kodokon.com

L'event loop : call stack, microtâches, macrotâches

Comprenez comment le moteur ordonnance réellement le code synchrone, les microtâches et les macrotâches.

10 min · 3 questions

Ouvrir cette leçon dans Kodokon

JavaScript exécute votre code sur un seul thread, mais l'event loop n'est pas défini par ECMAScript : c'est la spécification HTML (et libuv côté Node.js) qui décrit son fonctionnement. Le moteur maintient une call stack pour le code en cours, une file de macrotâches (timers, I/O, événements) et une file de microtâches (Promise.prototype.then, queueMicrotask, MutationObserver). La règle d'or : après chaque macrotâche, la file des microtâches est vidée entièrement avant de rendre la main.

JAVASCRIPT
console.log("script start");

setTimeout(() => console.log("timeout"), 0);

Promise.resolve()
  .then(() => console.log("promise 1"))
  .then(() => console.log("promise 2"));

console.log("script end");
// script start, script end,
// promise 1, promise 2, timeout
Les microtâches passent toujours avant les macrotâches.

Le script initial est lui-même une macrotâche : tant qu'il n'est pas terminé, rien d'autre ne s'exécute. Une fois la call stack vide, le moteur draine la file de microtâches, y compris celles ajoutées pendant le drainage. Ce détail explique pourquoi deux then chaînés s'exécutent avant un setTimeout(fn, 0) : chaque then replanifie une microtâche, et la file n'est jamais rendue tant qu'elle n'est pas vide.

JAVASCRIPT
setTimeout(() => console.log("macro 1"), 0);
setTimeout(() => console.log("macro 2"), 0);

queueMicrotask(() => {
  console.log("micro 1");
  queueMicrotask(() => console.log("micro 2"));
});
// micro 1, micro 2, macro 1, macro 2
Une microtâche imbriquée passe avant toute macrotâche.

Le mot-clé await s'appuie sur ce mécanisme : il suspend la fonction async, restitue le contrôle à l'appelant, puis planifie la reprise comme une microtâche. Même await null provoque cette suspension, car la valeur est enveloppée dans une promesse déjà résolue. C'est pourquoi le code situé après un await ne s'exécute jamais de façon synchrone.

JAVASCRIPT
async function main() {
  console.log("before await");
  await null;
  console.log("after await");
}

main();
console.log("sync code");
// before await, sync code, after await
await suspend la fonction et replanifie la suite.

Quiz de validation

Vérifiez que vous avez bien retenu les points clés de cette leçon.

  1. Un script affiche C en synchrone, planifie A via setTimeout(fn, 0) et B via Promise.resolve().then. Dans quel ordre les lettres s'affichent-elles ?
    • A, B, C
    • C, A, B
    • C, B, A
    • B, C, A
  2. Quand la file des microtâches est-elle vidée ?
    • Après chaque macrotâche, entièrement, y compris les microtâches ajoutées pendant le drainage
    • Une seule microtâche est exécutée entre deux macrotâches
    • Toutes les 16 ms, juste avant le rendu de la page
    • Uniquement lorsque toutes les macrotâches ont été traitées
  3. Que fait réellement await sous le capot ?
    • Il bloque le thread jusqu'à la résolution de la promesse
    • Il suspend la fonction async et planifie sa reprise comme microtâche
    • Il transforme le reste de la fonction en macrotâche
    • Il ne fait rien si la valeur attendue n'est pas une promesse