La boucle d’événements : pourquoi setTimeout(…, 0) n’est pas immédiat
Suivez l’ordre réel d’exécution de JavaScript : le code synchrone, puis les microtâches, puis les minuteurs et les événements.
Où en êtes-vous avec cette notion ?
Une indication personnelle, enregistrée uniquement dans ce navigateur.
À lire ensuite
Le format café
L’essentiel à comprendre, le temps d’un café.
Envie de creuser ? Un cours plus complet vous attend juste après, à déplier sans quitter cette page.
Ce que vous allez comprendre
Pourquoi setTimeout(…, 0) s’exécute après le reste du code, et pourquoi une Promise passe devant lui.
Les exemples ci-dessous s’observent dans la console d’un navigateur, ou avec node fichier.js. Ils ne demandent ni serveur ni installation.
Une seule chose à la fois
JavaScript exécute votre code une instruction à la fois. Les appels de fonction s’empilent dans la pile d’appels ; quand une fonction se termine, elle en sort et l’exécution reprend en dessous.
Précision utile : setTimeout n’appartient pas au langage. Il est fourni par l’environnement, navigateur ou Node.js. Le minuteur est donc géré à l’extérieur, et son rappel revient ensuite prendre place dans une file d’attente.
L’ordre surprend
console.log('A');
setTimeout(() => console.log('B'), 0);
console.log('C');
La console affiche A, C, puis B. setTimeout(…, 0) ne signifie pas « exécute immédiatement », mais « exécute dès que possible, après le code en cours ».
pile d’appels vide
↓
la boucle d’événements regarde la file
↓
le rappel de setTimeout est disponible
↓
console.log("B")
Deux files, pas une
console.log('A');
setTimeout(() => console.log('B'), 0);
Promise.resolve().then(() => console.log('C'));
console.log('D');
Le résultat est A, D, C, B. Les Promises passent par une file de microtâches, traitée en priorité avant les tâches ordinaires comme les rappels de setTimeout.
Code synchrone
↓
Microtâches (Promise, await)
↓
Tâches (setTimeout, événements)
C’est exactement le mécanisme derrière await : la suite de la fonction asynchrone est reprise sous forme de microtâche. Le code se lit presque comme du synchrone, alors que l’exécution reste asynchrone.
Ce classement est le modèle simplifié commun aux environnements, pas une spécification complète. Node.js possède des phases supplémentaires, avec par exemple setImmediate et process.nextTick, dont l’ordre relatif demande une lecture plus fine.
Le vrai piège
Un calcul long occupe la pile. Pendant ce temps, clics, animations et rappels attendent : c’est bloquer le thread principal. Découpez le travail en tâches plus petites, ou déplacez-le, par exemple dans un Web Worker.
Fiche mémo
| Notion | À retenir |
|---|---|
| Pile d’appels | Une seule chose à la fois |
setTimeout(…, 0) | Après le code en cours, pas immédiat |
| Microtâches | Promises et await, prioritaires |
| Tâches | Minuteurs, événements |
| Calcul lourd | Bloque réellement l’interface |
L’idée à emporter : rien n’est immédiat après le code en cours ; il y a seulement une file, et les microtâches y passent devant.
Et si on allait plus loin ?
Le café vous a donné les repères. Prenez maintenant le temps de comprendre les mécanismes et de pratiquer, si vous le souhaitez.
Aller plus loinPrédire l’ordre d’exécution avant de lancer le codeDévelopper le coursReplier le cours
Ce que vous saurez faire
Prédire l’ordre d’exécution d’un code mêlant appels directs, Promises et minuteurs, puis reconnaître ce qui bloque réellement l’interface.
Tous les exemples se collent dans la console d’un navigateur ou s’exécutent avec node fichier.js. Ils ne demandent ni serveur ni dépendance. Rappel important : setTimeout est fourni par l’environnement d’exécution, navigateur ou Node.js, et non par le langage lui-même. Le modèle décrit ici est le modèle simplifié commun aux deux ; Node.js ajoute des phases supplémentaires, dont setImmediate et process.nextTick, que ces trois étapes ne décrivent pas.
1. La pile d’appels décide de ce qui est « maintenant »
Tant qu’une fonction s’exécute, rien d’autre ne s’exécute. Un rappel ne peut donc jamais s’insérer au milieu de votre code.
function b() { console.log('b'); }
function a() { console.log('a'); b(); }
a();
console.log('fin');
La console affiche a, b, fin. b s’empile au-dessus de a, se termine, puis a se termine à son tour. Ce n’est qu’une fois la pile vidée que la boucle d’événements peut prendre un travail en attente.
Référence : setTimeout().
2. Deux files et un ordre stable
Après le code synchrone, l’environnement vide entièrement la file des microtâches — les réactions de Promises — avant de prendre une tâche, par exemple un rappel de minuteur.
console.log('1 synchrone');
setTimeout(() => console.log('4 tâche'), 0);
Promise.resolve()
.then(() => console.log('3 microtâche'))
.then(() => console.log('3 bis microtâche enchaînée'));
console.log('2 synchrone');
Résultat attendu : 1 synchrone, 2 synchrone, 3 microtâche, 3 bis microtâche enchaînée, 4 tâche. Le point remarquable est la microtâche enchaînée : créée pendant le traitement des microtâches, elle est traitée dans le même cycle, avant le minuteur.
Le délai passé à setTimeout est un minimum, pas un engagement. Le rappel attend la fin du code en cours, puis son tour dans la file. Les navigateurs imposent en outre généralement un plancher d’environ quatre millisecondes aux minuteurs profondément imbriqués.
Référence : la boucle d’événements.
3. await est un point de reprise, pas une pause du programme
Une fonction async s’exécute normalement jusqu’au premier await. Sa suite est reprise plus tard, sous forme de microtâche.
async function demo() {
console.log('2 avant await');
await null;
console.log('4 après await');
}
console.log('1 début');
demo();
console.log('3 après appel');
setTimeout(() => console.log('5 minuteur'), 0);
Résultat attendu : 1 début, 2 avant await, 3 après appel, 4 après await, 5 minuteur. await null suffit à provoquer la reprise différée : ce n’est pas la durée de l’attente qui compte, c’est le passage par la file des microtâches.
Pièges et limites
- Lire
setTimeout(…, 0)comme « tout de suite ». Le zéro est un délai minimal ; le rappel attend au moins la fin du code en cours. - Bloquer le thread principal. Une boucle de calcul longue occupe la pile : les clics ne sont pas traités, les animations se figent, les minuteurs se déclenchent en retard. Découpez le travail, ou confiez-le à un Web Worker.
- Affamer les tâches avec des microtâches. Une microtâche qui en programme systématiquement une nouvelle réalimente la file en continu. Le moteur ne passe alors jamais à la tâche suivante, et l’interface reste figée alors même qu’aucun calcul lourd ne tourne.
- Généraliser l’ordre observé. D’un environnement à l’autre, l’ordre entre minuteurs de même délai, entrées-sorties et rendu n’est pas garanti identique. Les microtâches avant les tâches, oui ; le reste demande de la prudence.
Référence : minuteurs Node.js.
À vous de jouer
Sans exécuter le code, écrivez l’ordre des cinq affichages. Vérifiez ensuite dans la console d’un navigateur.
console.log('un');
setTimeout(() => console.log('deux'), 0);
Promise.resolve().then(() => console.log('trois'));
queueMicrotask(() => console.log('quatre'));
console.log('cinq');
Correction commentée
L’ordre est : un, cinq, trois, quatre, deux.
un et cinq sont synchrones : ils s’exécutent pendant que la pile est occupée par le script lui-même. Rien d’autre ne peut passer avant eux.
trois et quatre sont deux microtâches. Elles s’exécutent dès que la pile est vide, dans leur ordre d’inscription : la réaction attachée à la Promise a été enregistrée avant l’appel à queueMicrotask, donc trois précède quatre.
deux est une tâche. Elle ne s’exécute qu’après que la file des microtâches a été entièrement vidée, même avec un délai de zéro. C’est là toute la démonstration : le zéro ne désigne pas un instant, mais une place dans une file.
La leçon n’est pas de retenir cette liste par cœur, mais le classement qui la produit : le code synchrone, puis toutes les microtâches, puis une tâche.
Vous pouvez aussi vous arrêter ici. L’approfondissement est facultatif.
Les repères Cours Café
Pour aller à la source
Documentation de référence. Vérification éditoriale encore à effectuer.
Gardez une trace de cette idée.
Favoris, notes et progression seront disponibles après connexion du stockage distant.