Boucle d’événements JavaScript expliquée : Pile d’appels, microtâches et minuteries avec exemples
Pourquoi un setTimeout de zéro s’exécute-t-il toujours en dernier ? Comment un thread peut-il gérer autant de choses à la fois ? Découvrez comment fonctionne réellement la boucle d’événements, testez-la avec le code que nous avons exécuté et résolvez la question d’entretien classique.
- JavaScript exécute votre code sur un un seul thread, une pièce à la fois.
- Lorsque la pile d’appels est vide, toutes les microtâches s’exécutent en premier, puis une tâche.
setTimeout(fn, 0)signifie « dès que possible après un délai minimum », et non « maintenant ».- Un long travail synchrone bloque tout, alors divisez-le ou déplacez-le vers un travailleur.
Exécutez quelques lignes avec setTimeout et une promesse, et l’ordre de sortie pourrait vous surprendre. La boucle d’événements JavaScript explique pourquoi. C’est le mécanisme qui permet à un langage monothread de gérer les minuteries, les appels réseau et les clics sans se figer.
Une fois que vous l’avez compris, toute une classe de bugs déroutants commence à avoir un sens. Nous avons exécuté chaque exemple sur Node.js 24, et le résultat provient de cette exécution. Les règles de classement de base s’appliquent également dans les navigateurs, et nous soulignons les différences entre Node.js. Vous en tirerez davantage si vous connaissez déjà les bases des Promesses JavaScript.
Pourquoi la boucle d’événements existe
JavaScript exécute votre code sur un thread principal, de sorte qu’un seul élément s’exécute à la fois. S’il attendait simplement une requête réseau, une page se figerait jusqu’à ce que la réponse revienne.
Donc JavaScript n’attend pas. Le travail lent est confié à l’environnement hôte, c’est-à-dire au navigateur ou à Node.js, qui peut le gérer en arrière-plan. Une fois ce travail terminé, un rappel est placé dans une file d’attente. Le travail de la boucle d’événements consiste à déplacer les rappels en file d’attente sur la pile lorsqu’elle est libre.
Pensez à un chef travaillant seul. Le chef met un plat au four et règle un minuteur, plutôt que de le regarder. Lorsque le minuteur sonne, la tâche est inscrite sur une liste et le chef la récupère après avoir terminé ce qui est actuellement en cours.
Les pièces mobiles
| Partie | Ce que ça fait | Exemples |
|---|---|---|
| Pile d’appels | Suit les fonctions en cours d’exécution | Votre code synchrone |
| API hôtes | Travaillez en dehors de votre code | Minuteries, fetch, lectures de fichiers |
| File d’attente des tâches | Contient les rappels prêts à être exécutés | setTimeout, clics, E/S |
| File d’attente de microtâches | Contient les rappels de priorité plus élevée | .then, await, queueMicrotask |
| Boucle d’événement | Décide de ce qui se passera ensuite | Fonctionne en continu |
Les tâches sont parfois appelées macrotâches. À proprement parler, il peut y avoir plusieurs sources de tâches, et les détails exacts diffèrent selon les environnements.
Les règles
- Exécutez tout le code synchrone jusqu’à ce que la pile d’appels soit vide.
- Exécutez toutes les microtâches, y compris les nouvelles ajoutées en cours de route.
- Dans un navigateur, effectuez une mise à jour si une mise à jour est due.
- Prenez une tâche dans une file d’attente et exécutez-la.
- Revenez à l’étape 2.
Le détail le plus important : toutes microtâches, puis une tâche. La plupart des énigmes de boucles d’événements sont résolues en appliquant cette seule règle.
Voir la commande
console.log("1 sync");
setTimeout(() => console.log("5 timeout"), 0);
Promise.resolve().then(() => console.log("3 promise"));
queueMicrotask(() => console.log("4 microtask"));
console.log("2 sync");
// 1 sync
// 2 sync
// 3 promise
// 4 microtask
// 5 timeout
Voici ce qui s’est passé. Les deux lignes console.log sont synchrones, elles ont donc été exécutées en premier. Le rappel de promesse et queueMicrotask sont allés dans la file d’attente des microtâches et ils ont été exécutés dès que la pile était vide, dans l’ordre dans lequel ils ont été ajoutés. Le rappel du minuteur devait attendre le tour d’une tâche, même si son délai était nul.
Les microtâches s’exécutent entre les minuteries
Après chaque tâche, la file d’attente des microtâches est à nouveau vidée. Cet exemple le prouve :
setTimeout(() => {
console.log("timer 1");
Promise.resolve().then(() => console.log("promise inside timer 1"));
}, 0);
setTimeout(() => console.log("timer 2"), 0);
// timer 1
// promise inside timer 1
// timer 2
La promesse, qui a été créée lors du premier minuteur, a été exécutée avant le deuxième minuteur. Les navigateurs modernes et Node.js 11 et versions ultérieures se comportent de cette façon.
Où l’attente correspond
await divise une fonction en deux. Tout avant son exécution de manière synchrone et tout ce qui suit est planifié sous forme de microtâche. Cela surprend beaucoup de gens :
async function task() {
console.log("A before await");
await null;
console.log("C after await");
}
task();
console.log("B after calling task");
// A before await
// B after calling task
// C after await
Une fonction async ne démarre pas en arrière-plan. Il s’exécute sur le même thread jusqu’à ce qu’il atteigne son premier await. Cela s’applique même lorsque l’on attend quelque chose qui n’est pas une vraie promesse, comme null. Consultez notre guide JavaScript async/await pour en savoir plus sur cette syntaxe.
Bloquer la boucle
Pendant l’exécution du code synchrone, rien d’autre ne peut le faire. Pour montrer cela, nous avons réglé un timer sur 0 ms, puis avons gardé le thread occupé pendant une demi-seconde :
const start = Date.now();
setTimeout(() => console.log("timer ran after", Date.now() - start, "ms"), 0);
while (Date.now() - start < 500) {}
// timer ran after about 500 ms
Le délai du minuteur est un minimum et non une garantie. Il ne fonctionnait que lorsque la pile devenait libre. Sur une page Web, une longue boucle comme celle-ci signifie que les clics ne répondent pas et que les animations s’arrêtent. Sur un serveur, toutes les autres requêtes doivent attendre, c’est pourquoi un appel synchrone lent peut nuire à tous les utilisateurs d’un processus Node.js.
Les causes courantes de blocage incluent d’énormes boucles, l’analyse de JSON très volumineux, des fonctions de fichiers synchrones comme readFileSync dans les gestionnaires de requêtes et des expressions régulières catastrophiques.
Astuce : Dans Chrome DevTools, le panneau Performances marque les tâches de plus de 50 ms comme des tâches longues. C’est un moyen rapide de trouver ce qui rend une page lente.
Rester réactif
Pour les travaux lourds, donnez à la boucle d’événements une chance de respirer. Une solution consiste à faire une pause sur une minuterie de temps en temps, ce qui permet à d’autres tâches de s’exécuter :
const items = [0, 1, 2, 3, 4];
async function processAll() {
for (const item of items) {
if (item === 2) await new Promise((r) => setTimeout(r, 0));
console.log("item", item);
}
}
setTimeout(() => console.log("other work gets a turn"), 0);
processAll();
// item 0
// item 1
// other work gets a turn
// item 2
// item 3
// item 4
L’autre chronomètre s’est déroulé au milieu, car nous avons cédé face à une vraie tâche. Le vrai code produirait un nombre d’éléments ou de millisecondes. Pour les travaux vraiment gourmands en CPU, tels que le traitement d’images, utilisez un Web Worker dans les navigateurs ou un thread de travail dans Node.js, qui s’exécutent sur des threads distincts.
Manque de microtâches
Étant donné que la boucle vide complètement la file d’attente des microtâches avant de continuer, une microtâche qui continue de planifier davantage peut arrêter tout le reste. Nous avons testé cela avec 100 000 microtâches enchaînées :
let count = 0;
let timerFired = false;
setTimeout(() => { timerFired = true; }, 0);
function spin() {
count++;
if (count < 100000) queueMicrotask(spin);
else console.log("timer fired during microtasks?", timerFired);
}
queueMicrotask(spin);
// timer fired during microtasks? false
Le chronomètre ne pouvait pas fonctionner tant que tous n’étaient pas terminés. Si cette chaîne ne se terminait jamais, les minuteries ne se déclencheraient jamais et, dans un navigateur, la page ne serait jamais repeinte. Notez que céder avec await sur une promesse résolue n’aide pas ici, car cela ajoute simplement une autre microtâche. Vous avez besoin d’une vraie tâche, comme un chronomètre, pour donner le tour aux autres.
Rendu dans les navigateurs
Un navigateur a généralement la possibilité de peindre entre les tâches. Cela explique un bug courant :
spinner.hidden = false;
doHeavyWork(); // blocks the thread
spinner.hidden = true;
Le spinner n’apparaît jamais, car le navigateur n’a pas eu la possibilité d’effectuer un rendu entre ces lignes. Pour y remédier, laissez passer un cadre avant le début des gros travaux, ou confiez ce travail à un ouvrier. Pour le code qui modifie les visuels, requestAnimationFrame s’exécute juste avant la prochaine peinture, ce qui en fait le bon outil pour l’animation.
Différences Node.js
Node.js possède sa propre boucle d’événements avec plusieurs phases, notamment des minuteries, des interrogations d’E/S et setImmediate. Il dispose également de process.nextTick, qui s’exécute dans sa propre file d’attente. Voici ce que nous avons vu lors de leur exécution depuis une fonction asynchrone :
setTimeout(() => console.log("timeout"), 0);
setImmediate(() => console.log("immediate"));
process.nextTick(() => console.log("nextTick"));
Promise.resolve().then(() => console.log("promise"));
// promise
// nextTick
// immediate
// timeout
De nombreux articles affirment que nextTick bat toujours les promesses et qu’un minuteur bat setImmediate. Notre résultat montre que ce n’est pas si simple. Notre code s’exécutait déjà dans une microtâche, donc la file d’attente des promesses s’est poursuivie en premier. Et l’ordre de setTimeout et setImmediate est connu pour varier en fonction de l’endroit d’où ils sont appelés. N’écrivez donc pas de code qui dépend de l’ordre exact de ceux-ci. Si une chose doit se produire après une autre, enchaînez-les explicitement.
Quiz d’entretien
Essayez de déterminer l’ordre avant de lire la réponse :
console.log("a");
setTimeout(() => console.log("b"), 0);
Promise.resolve().then(() => {
console.log("c");
setTimeout(() => console.log("d"), 0);
Promise.resolve().then(() => console.log("e"));
});
console.log("f");
Notre exécution a imprimé a, f, c, e, b, d. Voici pourquoi :
aetfsont synchrones, ils s’exécutent donc en premier.- La pile est vide, donc les microtâches s’exécutent :
c, puis la nouvelle microtâchee, dans le même tour. - Maintenant une tâche : le minuteur
b, qui a été mis en file d’attente en premier. - Puis la tâche suivante : le timer
d, qui a été mis en file d’attente pendant la microtâche.
Erreurs courantes
- La réflexion
setTimeoutest exacte. Le délai n’est qu’un minimum. - Penser
asyncsignifie un autre thread. Le code asynchrone s’exécute toujours sur le thread principal entre les attentes. - Utiliser les mises en veille pour corriger l’ordre. Attendre la condition réelle est beaucoup plus fiable, ce qui s’applique également aux tests irréguliers.
- Utilisation de
forEachavecawait. Il n’attend pas entre les éléments. Voir Méthodes de tableau JavaScript. - Effectuer un travail synchrone lourd dans un gestionnaire de serveur. Une requête peut retarder toutes les autres requêtes.
Pour conclure
N’oubliez pas la règle de base : exécutez la pile jusqu’à ce qu’elle soit vide, exécutez toutes les microtâches, puis exécutez une tâche et répétez. Tout le reste en découle. Pour que cela dure, écrivez votre propre version du quiz ci-dessus, prédisez le résultat sur papier, puis exécutez-le. Se tromper et comprendre pourquoi vous en apprendra plus que n’importe quel diagramme.
Questions fréquentes
Qu’est-ce que la boucle d’événements en JavaScript ?
C’est le mécanisme qui exécute les rappels en file d’attente lorsque la pile d’appels est vide. Il permet à JavaScript monothread de gérer les minuteries, les entrées et les réponses réseau sans blocage.
Quelle est la différence entre une microtâche et une tâche ?
Les microtâches, telles que les rappels de promesses, s’exécutent toutes dès que la pile est vide. Les tâches, telles que les minuteries, s’exécutent une par une, les microtâches étant épuisées après chacune.
Pourquoi setTimeout avec 0 ne s’exécute-t-il pas immédiatement ?
Son rappel doit attendre un tour de tâche. Tous les codes et microtâches synchrones s’exécutent en premier, et le délai est un minimum plutôt qu’une heure exacte.
JavaScript est-il monothread ?
Votre code JavaScript principal s’exécute sur un seul thread. Les navigateurs et Node.js utilisent d’autres threads pour certains travaux en arrière-plan, et les Workers vous permettent d’exécuter du code en parallèle.
Wait bloque-t-il la boucle d’événements ?
Non, wait met en pause uniquement cette fonction asynchrone, et d’autres codes peuvent s’exécuter entre-temps. Cependant, un code synchrone long avant ou après peut toujours bloquer la boucle.
Continuez à apprendre
Besoin d’aide pour les tests, WordPress ou le référencement ?
Nous aidons les équipes à proposer de meilleurs logiciels et à développer leurs sites. Dites-nous sur quoi vous travaillez et nous vous proposerons une prochaine étape claire.
Plus de guides sur tests, IA et développement moderne
Vous avez apprécié cet article ? Choisissez un sujet et accédez directement à nos nouveaux didacticiels.
Les hallucinations de l'IA expliquées : pourquoi les modèles de langage inventent les choses et comment les réduire
Les hallucinations de l'IA ont expliqué : ce qu'elles sont, pourquoi les modèles de langage les produisent, les types courants, comment les réduire …
Lire l'article →DevOpsVariables d'environnement expliquées : fichiers .env, secrets et configuration correctement effectués
Variables d'environnement expliquées avec des exemples Python que nous avons exécutés : lecture et définition, pourquoi chaque valeur est une chaîne…
Lire l'article →Développement logicielLimitation du débit de l'API expliquée : algorithmes, en-têtes et gestion des erreurs 429
Limitation du débit de l'API expliquée avec une simulation que nous avons exécutée : compartiment de jetons, fenêtre fixe, fenêtre glissante, 429 ré…
Lire l'article →Automatisation des testsSe moquer en Python : unittest.mock, patch et les erreurs qui font mentir les tests
Moquerie en Python avec unittest.mock, testée : Mock et return_value, side_effect, correctifs où un nom est utilisé, spec et autospec, dates moqueus…
Lire l'article →Tests logicielsCouverture du code expliquée : couverture des lignes par rapport aux branches et ce que signifie réellement 100 %
Couverture du code expliquée avec une véritable exécution de cover.py : couverture des lignes, des branches et des conditions, lecture d'un rapport,…
Lire l'article →Bases de données et SQLDELETE vs TRUNCATE vs DROP dans SQL : différences, restauration et quand les utiliser
DELETE vs TRUNCATE vs DROP expliqué avec SQL testé : ce que chacun supprime, restauration, déclencheurs, réinitialisation d'identité, vitesse et en …
Lire l'article →Bases de données et SQLVues SQL expliquées : créez, utilisez et évitez les pièges, avec des exemples
Vues SQL expliquées avec les requêtes que nous avons exécutées : création d'une vue, résultats en direct, masquage des colonnes, performances, vues …
Lire l'article →Développement logicielInterface TypeScript vs Type : différences réelles, testées avec le compilateur
Interface TypeScript vs type, vérifiée avec le vrai compilateur : ce que les deux peuvent faire, ce qu'un seul peut faire, la fusion des déclaration…
Lire l'article →Développement logicielPython *args et **kwargs expliqués : fonctions flexibles avec des exemples clairs
Python *args et **kwargs expliqués avec le code que nous avons exécuté : emballage et déballage, ordre des paramètres, arguments de mot-clé uniqueme…
Lire l'article →Développement logicielFonctions Python Lambda : syntaxe, utilisations réelles et quand ne pas les utiliser
Fonctions Python lambda expliquées avec le code que nous avons exécuté : syntaxe, tri avec clé, carte et filtre, fermetures, piège de liaison tardiv…
Lire l'article →Intelligence artificielleQue sont les agents IA ? Comment ils fonctionnent, avec une simple boucle d'agent que vous pouvez exécuter
Que sont les agents IA ? Un guide simple sur la boucle d'agent, les outils, la mémoire et les garde-fous, avec une petite boucle Python que nous avo…
Lire l'article →DevOpsSurveillance vs observabilité : la vraie différence, avec les journaux, les métriques et les traces
Surveillance et observabilité expliquées : échecs connus et inconnus, journaux, métriques et traces, présentation pas à pas des incidents de paiemen…
Lire l'article →DevOpsVolumes Docker vs montages Bind : conserver les données du conteneur de la bonne manière
Volumes Docker et montages de liaison expliqués : pourquoi les données du conteneur disparaissent, volumes nommés, montages de liaison, tmpfs, exemp…
Lire l'article →Automatisation des testsTDD vs BDD : principales différences, une fonctionnalité conçue dans les deux sens et quand les utiliser
TDD vs BDD expliqué avec une fonctionnalité de remise construite dans les deux sens : refactor rouge-vert avec le code que nous avons exécuté, scéna…
Lire l'article →Tests logicielsTests d'acceptation utilisateur (UAT) : processus, liste de contrôle et exemples réels
Les tests d'acceptation des utilisateurs sont expliqués : qui les exécute, en quoi ils diffèrent du contrôle qualité, un processus en sept étapes, u…
Lire l'article →Bases de données et SQLExplication des valeurs SQL NULL : IS NULL, COALESCE, NULLIF et les pièges à éviter
Les valeurs SQL NULL expliquées avec les requêtes que nous avons exécutées : logique à trois valeurs, IS NULL, COALESCE, NULLIF, comment les agrégat…
Lire l'article →Bases de données et SQLInstruction SQL CASE WHEN : syntaxe, exemples réels et erreurs courantes
SQL CASE WHEN expliqué avec les requêtes que nous avons exécutées : formulaires simples et recherchés, ordre des conditions, pivots avec SUM, astuce…
Lire l'article →Développement logicielQu'est-ce que JWT ? Les jetons Web JSON expliqués avec un exemple fonctionnel
Qu’est-ce que JWT ? JSON Web Tokens expliqué : les trois parties, un jeton construit et falsifié dans le code que nous avons exécuté, JWT vs session…
Lire l'article →Développement logicielBoucle d'événements JavaScript expliquée : pile d'appels, microtâches et minuteries avec des exemples
La boucle d'événements JavaScript expliquée avec le code que nous avons exécuté : pile d'appels, files d'attente de tâches et de microtâches, pourqu…
Lire l'article →Développement logicielGénérateurs Python et rendement expliqués : économisez de la mémoire avec des exemples réels
Les générateurs Python et le rendement expliqués avec le code que nous avons exécuté : comment ils s'arrêtent et reprennent, les économies de mémoir…
Lire l'article →Intelligence artificielleQue sont les intégrations ? Vecteurs et similarité cosinus expliqués simplement
Que sont les intégrations ? Un guide en langage simple sur les vecteurs, la similarité cosinus et la recherche sémantique, avec un petit exemple que…
Lire l'article →DevOpsDéploiement Bleu-Vert vs Canary : différences, compromis et comment choisir
Explication du déploiement bleu-vert ou canari : comment chacun fonctionne, coût, risque et restauration, regroupement d'utilisateurs stable que nou…
Lire l'article →Automatisation des testsTests instables : quelles sont leurs causes et comment les corriger définitivement
Des tests irréguliers expliqués avec des démos que nous avons exécutées : les principales causes, comment en confirmer et en diagnostiquer une, les …
Lire l'article →Tests logicielsTests de charge vs tests de contrainte : différences, métriques et exemples k6
Explication des tests de charge et des tests de stress : ce que chacun prouve, les tests de pointe et de trempage, les métriques qui comptent, pourq…
Lire l'article →Tests logicielsScénario de test et scénario de test : principales différences, exemples et quand les utiliser
Scénario de test et scénario de test expliqués : des définitions claires, un tableau côte à côte, une fonctionnalité de réinitialisation de mot de p…
Lire l'article →Bases de données et SQLOptimisation des requêtes SQL : lire les plans d'exécution et corriger les requêtes lentes, avec des délais
Optimisation des requêtes SQL testée sur un million de lignes : lire les plans d'exécution, ajouter les bons index, corriger les filtres non Sargabl…
Lire l'article →Bases de données et SQLSQL UNION vs UNION ALL : différences, exemples et performances testées
SQL UNION vs UNION ALL expliqué avec les requêtes que nous avons exécutées : des doublons, les règles suivent toutes les deux, un véritable bug de d…
Lire l'article →Développement logicielTutoriel API JavaScript Fetch : GET, POST, gestion des erreurs et délais d'attente
Un didacticiel de l'API JavaScript Fetch testé sur un serveur local : GET et POST, pourquoi 404 ne lance pas, un assistant réutilisable, des chaînes…
Lire l'article →Développement logicielGestion des fichiers Python : lecture, écriture, CSV, JSON et pathlib avec exemples
Gestion des fichiers Python expliquée avec le code que nous avons exécuté : modes ouverts, avec lecture de fichiers volumineux, CSV, JSON, pathlib, …
Lire l'article →Développement logicielClasses et objets Python : didacticiel POO avec des exemples réels
Classes et objets Python expliqués avec le code que nous avons exécuté : __init__, méthodes, attributs de classe par rapport à l'instance, héritage,…
Lire l'article →Intelligence artificielleQu'est-ce qu'un grand modèle de langage ? Comment fonctionnent les LLM, expliqués sans battage médiatique
Qu’est-ce qu’un grand modèle de langage ? Les LLM expliqués en langage simple : jetons, intégrations, attention, formation, fenêtres de température …
Lire l'article →DevOpsCommandes Linux pour les débutants : le guide du terminal essentiel avec des exemples
Commandes Linux pour débutants, regroupées par tâche : navigation, fichiers, affichage des journaux, grep, tuyaux, autorisations, processus et archi…
Lire l'article →Automatisation des testsSelenium vs Playwright : principales différences, code comparé et lequel choisir
Comparatif Selenium vs Playwright : architecture, attente, support du navigateur et du langage, débogage, même test dans les deux outils, conseils d…
Lire l'article →Tests logicielsPartitionnement d'équivalence : technique de conception de tests expliquée avec des exemples concrets
Le partitionnement d'équivalence expliqué : comment trouver des partitions valides et invalides, des exemples concrets d'âges, de mots de passe et d…
Lire l'article →Tests logicielsGravité vs priorité dans les tests logiciels : différences, matrice et exemples réels
Explication de la gravité et de la priorité dans les tests logiciels : des définitions claires, une matrice à quatre voies avec des exemples, des éc…
Lire l'article →Bases de données et SQLTutoriel sur les sous-requêtes SQL : types, sous-requêtes corrélées et EXISTE avec des exemples
Un didacticiel sur les sous-requêtes SQL avec les requêtes que nous avons exécutées : les sous-requêtes scalaires, IN, FROM et SELECT, les sous-requ…
Lire l'article →Bases de données et SQLTransactions SQL et ACID expliquées : COMMIT, ROLLBACK et niveaux d'isolement
Transactions SQL et ACID expliquées avec un virement bancaire que nous avons effectué : COMMIT, ROLLBACK, points de sauvegarde, verrous, niveaux d'i…
Lire l'article →Développement logicielGit merge vs rebase : différences, exemples et quand utiliser chacun
Git merge vs rebase expliqué avec de vraies sorties de log, un tableau comparatif, la règle d'or du rebase et un flux de travail sûr pour les équipes.
Lire l'article →Développement logicielList comprehension Python : syntaxe, exemples et cas à éviter
La list comprehension Python expliquée : syntaxe, filtres, if-else, boucles imbriquées, temps mesurés et cas où une boucle for classique est préféra…
Lire l'article →Bases de données et SQLJointures SQL expliquées avec exemples (Inner, Left, Right, Full)
Comprenez les jointures SQL pas à pas avec des tables et requêtes d'exemple : INNER, LEFT, RIGHT, FULL, CROSS et SELF, plus erreurs courantes et con…
Lire l'article →