Event loop en JavaScript : comment un seul fil exécute du code asynchrone

La boucle d'événements orchestre un langage à fil unique : pile d'appels, file des tâches, file des microtâches, et ce qui arrive quand on la bloque.
3 min de lecture
Believemy logo

JavaScript n'exécute qu'une chose à la fois. Pourtant une page anime un bandeau, écoute cinq boutons et attend trois requêtes sans jamais se figer.

Ce tour de force ne vient pas du langage lui-même, mais du mécanisme qui décide de l'ordre de passage : la boucle d'événements.


Définition

Composez un programme, la boucle choisit un autre ordre
Ajouter une ligne :
Votre programmeRang de sortie
1console.log("A");→ 1re
2setTimeout(() => console.log("B"), 0);→ 4e
3Promise.resolve().then(() => console.log("C"));→ 3e
4console.log("D");→ 2e
Étape 1 / 9
Rien n'a encore tourné. La pile est vide, les deux files aussi.
Pile d'appels
vide
Microtâches
vide
Tâches
vide
Console
ADCB
Ordre d'écriture : A B C D
Lignes déplacées : 2 sur 4Écart maximal : 2 rangs

L'ordre d'affichage n'est pas l'ordre d'écriture. Le script se déroule d'abord en entier, puis la file des microtâches se vide complètement, et une tâche seulement passe ensuite. Un .then inscrit après un setTimeout s'exécute donc avant lui.

Tous les setTimeout portent un délai de zéro, les tâches sont donc servies dans leur ordre d'inscription ; avec des délais différents, c'est l'échéance qui décide entre elles. La démo montre le callback rejoindre la file dès son inscription, alors que l'environnement l'y dépose une fois le minuteur écoulé : un délai de zéro vaut en réalité une milliseconde environ, ce qui ne change rien ici puisque le script se termine avant. Sont également laissés de côté le temps d'exécution, le rendu de la page et les phases supplémentaires de Node.js.

Composez un programme ci-dessus et comparez l'ordre d'écriture à l'ordre de sortie. C'est l'écart entre les deux qui explique la plupart des surprises de l'asynchrone.

La boucle d'événements est le mécanisme qui, en permanence, regarde si la pile d'appels est vide et, si elle l'est, y transfère la prochaine tâche en attente. Trois pièces suffisent à la décrire.

PièceCe qu'elle contient
La pile d'appelsLes fonctions en cours d'exécution, empilées
La file des tâchesLes callbacks des minuteurs, des événements, des requêtes
La file des microtâchesLes suites de promesses, servies en priorité

Un setTimeout ne décompte pas dans le moteur : il est confié à l'environnement, navigateur ou Node.js, qui déposera le callback dans la file une fois le délai écoulé.


Un tour de boucle

La règle tient en trois temps : dérouler le code en cours jusqu'au bout, vider entièrement la file des microtâches, puis seulement prendre une tâche.

JAVASCRIPT
console.log("1 synchrone");

setTimeout(() => console.log("4 tâche"), 0);

Promise.resolve().then(() => console.log("3 microtâche"));

console.log("2 synchrone");

// 1 synchrone
// 2 synchrone
// 3 microtâche
// 4 tâche

Le délai de zéro milliseconde ne signifie donc pas tout de suite, mais dès que la pile sera libre. Une Microtâche passe systématiquement devant, même inscrite après.


Bloquer la boucle

Puisque tout partage le même fil, une fonction longue empêche tout le reste de se produire : aucun clic n'est traité, aucune animation ne progresse, aucun minuteur ne se déclenche.

JAVASCRIPT
const fin = Date.now() + 2000;
while (Date.now() < fin) {}   // deux secondes de page gelée
console.log("la page redevient vivante");

Un calcul lourd se découpe donc en tranches rendues à la boucle entre deux morceaux, ou part dans un fil séparé. C'est la même contrainte côté serveur : sous Node.js, une fonction bloquante retient toutes les requêtes en cours.

Bon à savoir

Node.js suit le même principe avec des phases supplémentaires, dont celle des minuteurs et celle des entrées-sorties. D'où l'existence de setImmediate, qui place un callback à la phase suivante sans passer par un délai.


Questions fréquentes

Question

Le code asynchrone s'exécute-t-il en parallèle ?

Non, pas le code JavaScript. L'attente, elle, est bien parallèle : le téléchargement et le décompte d'un minuteur sont pris en charge par l'environnement. Mais les callbacks reviennent tous sur le même fil et passent chacun leur tour.


Question

Pourquoi mon setTimeout à 100 ms se déclenche-t-il plus tard ?

Parce que le délai indique le moment à partir duquel le callback devient éligible, pas le moment de son exécution. Si la pile est occupée quand l'échéance arrive, le callback attend qu'elle se libère. C'est un plancher, jamais une garantie.


Question

Comment sortir un calcul lourd de la boucle ?

Dans un navigateur, un Web Worker exécute le calcul dans un fil séparé et communique par messages. Sous Node.js, la même idée passe par un processus enfant ou un fil de travail. Ces découpages structurent la performance d'un serveur, un sujet travaillé dans la formation Node.js.

Termes connexes

Découvrez notre glossaire JavaScript

Tous les mots de JavaScript expliqués simplement : mots-clés, objets natifs, méthodes, erreurs et concepts. Définitions claires et exemples qui tournent, pour apprendre et pour se dépanner.

Partager cet article

Tu veux nous aider ? Fais un lien vers cet article sur tes réseaux ou encore mieux : sur ton site, dans un article ou dans ta newsletter.