Accueil / Articles

Comprendre les fermetures de JavaScript et le cycle d’événements

Les fermetures JavaScript maintiennent les variables externes en vie ; le cycle d’événements organise la pile d’appels, les microtâches et les macrotâches. Tout cela est expliqué à l’aide d’exemples de code exécutables.

Éditeur : REACTAPP.TOP16 min de lecture3605 mots

Sources: JavaScript Closures Explained in 2 Minutes, The JavaScript Event Loop — Finally Explained With Zero Confusion

Le JavaScript est souvent décrit comme un langage plein de particularités, mais deux de ses comportements les plus mal compris — les fermetures et le cycle d’événements — proviennent en réalité de la même idée fondamentale : les fonctions et les tâches planifiées ne disparaissent pas simplement parce que le code qui les entoure a continué. Comprendre comment une fonction peut « se souvenir » des variables après le retour de sa fonction parente, et comment l’environnement d’exécution décide quand exécuter du code qui n’est pas immédiatement prêt, nécessite de cesser de voir le JavaScript comme un simple script ligne par ligne et de commencer à le considérer comme un système doté de règles de mémoire et de temporisation. Cet article explique ces deux concepts, en commençant par les fermetures.

Comprendre les fermetures

Qu’est-ce qu’une fermeture en réalité ?

Une fermeture se produit lorsque une fonction conserve des variables provenant du contexte dans lequel elle a été créée, et peut encore y accéder même après que la fonction externe ait terminé son exécution.

Une façon utile d’imaginer cela est de considérer une fonction qui emporte avec elle un petit sac contenant des variables, où qu’elle aille.

function createCounter() {
  let count = 0;
return function () {
    count++;
    return count;
  };
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3

Remarquez que createCounter() s’exécute et renvoie une valeur, mais la fonction interne qu’elle retourne conserve néanmoins un accès à count. Cet accès persistant, même après que l’appel externe soit techniquement terminé, constitue la fermeture.

Situations typiques où apparaissent les fermetures

Les fermetures sont importantes chaque fois qu’une fonction a besoin de conserver des données après que la fonction qui l’a créée a déjà quitté son exécution. Vous rencontrerez ce schéma dans plusieurs situations courantes :

  • Variables privées – permettent de cacher des données afin que du code extérieur ne puisse pas y accéder directement.
  • Fabriques de fonctions – création de fonctions personnalisées en fonction de paramètres.
  • Appels en retour – capture de valeurs afin qu’elles restent accessibles lorsque l’appel en retour s’exécute finalement.
  • Timers – accès à des variables depuis l’intérieur de setTimeout ou setInterval.
  • Gestionnaires d’événements – mémorisation de données liées à un événement spécifique.
  • React – compréhension des appels en retour, des effets et du problème de la « closure obsolète ».
  • Pourquoi c’est important

    Les closures permettent de conserver l’état sans recourir à des variables globales, ce qui rend le code plus propre et plus sûr.

    function createAccount(initialBalance) {
      let balance = initialBalance;
      return {
        deposit(amount) {
          balance += amount;
        },
        getBalance() {
          return balance;
        }
      };
    }
    const account = createAccount(100);
    account.deposit(50);
    console.log(account.getBalance()); // 150
    console.log(account.balance);      // undefined
    

    Dans cet exemple, balance ne peut pas être atteint directement depuis l’extérieur de la fonction. Seuls les méthodes de l’objet, retournées par createAccount, ont accès à lui.

    Cette structure vous offre plusieurs avantages réels :

    • Mieux encapsulation
    • État véritablement privé
    • Code plus simple et lisible
    • Dépendance réduite envers les variables globales

    L’exemple classique de boucle

    Une démonstration courante des fonctions de fermeture en action concerne la différence entre l’utilisation de var et de let à l’intérieur d’une boucle qui planifie des appels de fonction.

    Voici ce qui se passe avec var :

    for (var i = 1; i <= 3; i++) {
      setTimeout(() => {
        console.log(i);
      }, 1000);
    }
    // Output:
    // 4
    // 4
    // 4
    

    Le résultat n’est pas celui que beaucoup de gens attendent au début, et la raison en est que var a un champ d’application au niveau de la fonction plutôt qu’au niveau du bloc. Les trois appels de fonction setTimeout font tous référence à la même variable i. Lorsque l’un d’eux s’exécute enfin, la boucle est déjà terminée et i a déjà atteint la valeur 4.

    Comparez maintenant cela avec let :

    for (let i = 1; i <= 3; i++) {
      setTimeout(() => {
        console.log(i);
      }, 1000);
    }
    // Output:
    // 1
    // 2
    // 3
    

    Puisque let a un champ d’application par bloc, chaque itération de la boucle dispose de sa propre liaison indépendante pour i. Cela signifie que chaque fonction de rappel conserve une valeur différente, ce qui fait que les nombres s’affichent dans l’ordre auquel on s’y attendrait naturellement.

    Un échange courant lors d’un entretien

    Question : Qu’est-ce qu’une fermeture en JavaScript ?

    Une fermeture est une fonction qui se souvient et peut encore accéder aux variables de son contexte d’initialisation, même après que la fonction externe ait terminé son exécution.

    Suivi : Où utiliserait-on réellement des fermetures ?

    Les closures apparaissent le plus souvent dans les callbacks, les timers, les gestionnaires d’événements et les usines de fonctions. Elles sont également utiles chaque fois qu’une partie d’état doit rester privée plutôt que de vivre dans le scope global.

    En résumé

    L’idée fondamentale se résume à une équation : une fonction plus les variables qui l’entourent égalent une closure. Si l’image du sac à dos reste gravée dans votre esprit — une fonction qui emporte avec elle les variables qu’elle a capturées où que l’on l’appelle — tout le concept devient beaucoup moins mystérieux.

    Comprendre le cycle d’événements

    Les closures expliquent comment une fonction peut conserver des données dans le temps. Le cycle d’événements explique comment JavaScript décide quand exécuter réellement du code qui n’est pas prêt immédiatement — et c’est là que proviennent de nombreux résultats de console confus.

    Presque tous les développeurs JavaScript finissent par tomber sur une version de ce fragment exact, que ce soit lors d’un entretien ou par hasard :

    console.log("1");
    
    setTimeout(() => {
      console.log("2");
    }, 0);
    
    console.log("3");
    

    En lisant cela de haut en bas, on s’attendrait à ce que la console affiche d’abord 1, puis 2, puis 3 — le délai est fixé à zéro millisecondes, il ne devrait donc y avoir aucun retard.

    Mais en le lançant dans la console d’un navigateur ou sous Node, le résultat réel est le suivant :

    1
    3
    2
    

    Alors pourquoi un code programmé pour s’exécuter « immédiatement » s’exécute-t-il en réalité après le code écrit plus tard dans le fichier ?

    Pour y répondre, il faut comprendre le mécanisme qui sous-tend votre code : la boucle d’événements. Plutôt que de se perdre dans un vocabulaire formel concernant les contextes d’exécution et les algorithmes de planification, il est utile de se construire un modèle mental simple et fiable de ce qui se passe réellement.

    La contradiction fondamentale au cœur de JavaScript

    Au préalable des diagrammes et des définitions, il y a un fait que vous devez bien comprendre :

    JavaScript est strictement single-threadé.

    Être single-threadé signifie que JavaScript fonctionne avec une seule pile d’appels. Il dispose d’un seul « cerveau » de traitement et ne peut exécuter qu’une seule ligne de code à un instant donné. Il ne peut pas lancer deux fonctions en parallèle, et il ne peut pas non plus diviser un calcul sur plusieurs cœurs de manière autonome.

    Cette contrainte soulève une question évidente. Imaginez un morceau de code qui doit attendre trois secondes entières que une API lente réponde — avec un seul thread disponible et sans aucun autre élément pour aider, que peut faire le reste de la page pendant cette attente ?

    Dans un environnement purement à thread unique sans aucun soutien, tout le navigateur se bloquerait. Vous ne pourriez rien cliquer, faire défiler la page, taper dans un champ ou regarder une animation se dérouler — tout resterait figé jusqu’à ce que la réponse réseau arrive enfin.

    Mais ce n’est pas ce qui se passe réellement. Les applications web modernes diffusent régulièrement des vidéos, envoient des requêtes réseau en arrière-plan, mettent à jour silencieusement les données analytiques et acceptent des entrées clavier en même temps, sans se bloquer.

    Alors comment un langage à thread unique parvient-il à faire cela ?

    La réponse : JavaScript lui-même est à thread unique, mais l’environnement qui l’entoure — le navigateur ou Node.js — ne l’est pas.

    Une cuisine de restaurant comme modèle mental

    Le moyen le plus clair de comprendre cette architecture est d’imaginer une cuisine de fast-food très active :

    [ Customer Orders ] ──> [ The Solo Chef ] (Call Stack)
                                  │
                                  ▼ Hands off slow jobs
                            [ The Kitchen Staff ] (Web APIs)
                                  │
                                  ▼ Ready order slips
                            [ Order Counter ] (Queues)
                                  ▲
                                  │ Checks constantly
                            [ The Floor Manager ] (The Event Loop)
    

    Voici comment se répartissent les rôles :

    1. Le chef solo (la pile d’appels). Rapide et compétent, mais capable de travailler uniquement sur une commande à la fois — en la terminant complètement avant d’en commencer une autre.
    2. Les assistants de cuisine (les API web).Lorsqu’une commande nécessite un long temps d’attente — à cause d’un minuteur ou d’une réponse lente du livreur — le chef ne reste pas là à regarder l’heure. Au lieu de cela, il remet la commande à un assistant avec pour instruction de veiller dessus et de faire rapport dès qu’elle est prête, tandis que le chef reprend sa tâche en cours.
    3. Le comptoir des commandes (les files d’attente de rappels et de micro-tâches).Lorsque le minuteur s’arrête, l’assistant ne perturbe pas le chef en plein travail en lui mettant brusquement la commande entre les mains. Cela créerait du chaos. Au lieu de cela, la commande terminée est placée sur le comptoir en attente.
  • Le gestionnaire du sol (la boucle d’événements). Ce gestionnaire va sans cesse de l’accueil au chef, en suivant une règle stricte : ne jamais interrompre le chef pendant qu’il travaille. Dès que les mains du chef sont complètement libres, le gestionnaire prend le prochain ticket en attente à l’accueil et le lui remet.
  • C’est là tout le système en miniature. En gardant à l’esprit l’image du restaurant, il est temps de remplacer ces rôles par leurs noms réels dans le moteur JavaScript.

    Le moteur donne des noms différents à ces quatre éléments, mais chacun correspond directement à un rôle du restaurant.

    Le diagramme ci-dessous illustre comment l’environnement de exécution (navigateur ou Node.js) entoure le moteur JavaScript et relie ces éléments entre eux.

    ┌───────────────────────────────────────────────────────────┐
    │                     BROWSER / RUNTIME                     │
    │                                                           │
    │   ┌─────────────────┐             ┌───────────────────┐   │
    │   │   CALL STACK    │             │     WEB APIs      │   │
    │   │ (JS Engine/V8)  │             │                   │   │
    │   │                 │ ──────────> │  setTimeout()     │   │
    │   │  fnA()          │             │  fetch()          │   │
    │   │  console.log()  │             │  DOM Events       │   │
    │   └────────┬────────┘             └─────────┬─────────┘   │
    │            ▲                                │             │
    │            │                                │ Finished    │
    │            │                                ▼             │
    │            │                      ┌───────────────────┐   │
    │            │                      │  MICROTASK QUEUE  │   │
    │            │                      │  (Promises, VIP)  │   │
    │            │                      └─────────┬─────────┘   │
    │            │                                │             │
    │      EVENT LOOP                             ▼             │
    │   "Is Stack empty?"               ┌───────────────────┐   │
    │            └───────────────────── │  MACROTASK QUEUE  │   │
    │                                   │ (Timers, Clicks)  │   │
    │                                   └───────────────────┘   │
    └───────────────────────────────────────────────────────────┘
    

    La pile d’appels — « ce qui se passe en ce moment »

    La pile d’appels est une structure ordinaire LIFO (Dernier entré, premier sorti) gérée directement par le moteur JavaScript — il s’agit du même mécanisme utilisé dans V8 sous Chrome et Node, ou JavaScriptCore sous Safari.

    Chaque fois que vous appelez une fonction, elle est ajoutée en haut de cette pile. Lorsque cette fonction renvoie ou termine son exécution, elle est retirée du sommet.

    function stepThree() {
      console.log("Done!");
    }
    
    function stepTwo() {
      stepThree();
    }
    
    function stepOne() {
      stepTwo();
    }
    
    stepOne();
    

    Suivre une petite chaîne d’appels comme celle-ci montre clairement le fonctionnement :

    • stepOne est ajouté en haut de la pile.
    • stepOne appelle stepTwo, ce qui fait que stepTwo est ajouté en haut de la pile.
    • stepTwo appelle stepThree, ce qui fait que stepThree est ajouté en haut de la pile.
    • stepThree exécute console.log, ce qui affiche « Done! », puis il est retiré de la pile.
  • stepThree est terminé, il est donc retiré de la pile.
  • stepTwo est terminé, il est donc retiré de la pile.
  • stepOne est terminé, il est donc retiré de la pile.
  • La pile redevient vide.
  • Si l’une des fonctions présentes dans cette pile a besoin, disons, de dix secondes consécutives pour effectuer des calculs intensifs, tout le système s’arrête pendant ces dix secondes. Rien d’autre ne peut fonctionner tant que la pile n’est pas vidée — c’est là la conséquence directe du fait que le « chef » n’a que deux mains.

    Web APIs — où a lieu réellement l’« attente »

    Voici quelque chose qui surprend beaucoup de personnes au début de leur carrière : setTimeout, fetch et les écouteurs d’événements DOM ne font pas partie des fonctionnalités de la langue JavaScript elle-même.

    Vous ne les trouverez nulle part dans la spécification ECMAScript. Ils sont fournis par l’environnement qui héberge votre code — le navigateur, ou, dans le cas de Node.js, une combinaison d’bindings en C++ et de la bibliothèque libuv.

    Considérez cette appel :

    setTimeout(callback, 2000);
    

    Lorsque la pile d’appels exécute cette invocation de setTimeout, tout ce qu’elle fait réellement, c’est indiquer au mécanisme de comptage temporel interne du navigateur de commencer à décompter 2000 millisecondes. Dès que cette instruction est transmise, setTimeout lui-même est immédiatement retiré de la pile d’appels, et l’exécution se poursuit avec ce qui suit dans votre code. Le décompte lui-même a lieu entièrement en dehors du thread unique de JavaScript, dans l’environnement hôte.

    La file des macro-tâches — la file ordinaire

    Cette file est également appelée file des appels de retour ou file des tâches, selon à qui vous le demandez.

    Dès qu’une API web termine sa tâche en arrière-plan — un chronomètre atteint zéro, une réponse réseau arrive complètement, un utilisateur clique sur quelque chose — l’environnement hôte place la fonction de rappel associée dans cette file d’attente.

    Tout ce qui se trouve dans cette file d’attente se comporte de manière ordonnée : il s’agit d’une file standard FIFO (Premier entré, premier sorti), où chaque élément attend son tour d’être exécuté sur la pile d’appels.

    Des exemples typiques de macro-tâches incluent :

    • Les fonctions de rappel planifiées par setTimeout() et setInterval()
    • Les écouteurs d’événements DOM tels que click, scroll ou keydown
    • setImmediate(), propre à Node.js

    La file d’attente des micro-tâches — la voie rapide

    Parallèlement à cette file d’attente ordinaire, il existe une file plus petite mais ayant une priorité bien plus élevée : la file d’attente des micro-tâches.

    Tout ce qui est placé ici passe en tête de la file d’attente. Dès que la pile d’appels devient vide, le cycle d’événements examine d’abord la file de micro-tâches — avant même de considérer ce qui attend dans la file de macro-tâches.

    Les éléments qui se retrouvent dans la file de micro-tâches incluent :

    • Les gestionnaires attachés via .then(), .catch() ou .finally() sur des Promises
    • Le code qui s’exécute après un await à l’intérieur d’une fonction async
    • Tout ce qui est planifié avec queueMicrotask()
    • Les appels de retour déclenchés par un MutationObserver

    La règle fondamentale qui guide le cycle

    En mettant de côté les métaphores, le cycle d’événements n’est en réalité qu’un boucle while infinie qui s’exécute dans l’environnement hôte, suivant en boucle cinq étapes strictes :

    1. Vider la pile d’appels. Continuer à exécuter du code synchrone jusqu’à ce qu’il ne reste absolument rien sur la pile.
    2. Vider complètement la file des microtâches. Tant qu’il y a une microtâche en attente, l’exécuter. Si son exécution génère une autre microtâche, l’exécuter également. La boucle ne passera pas à l’étape suivante tant que la file des microtâches n’aura plus rien à traiter.
    3. Gérer l’affichage, si vous êtes dans un navigateur. Le moteur vérifie s’il est temps de redessiner l’écran — généralement environ 60 fois par seconde, soit tous les 16,6 millisecondes — et, le cas échéant, il recalcule la mise en page puis redessine l’écran.
    4. Exécuter une seule macrotâche. Examiner la file des macrotâches, sélectionner celle qui attend le plus longtemps, l’ajouter à la pile d’appels et la laisser s’exécuter jusqu’à son terme.
    5. Recommencer le cycle depuis l’étape un.

    Le détail qui pose problème, c’est cette asymétrie : les microtâches sont entièrement exécutées, chacune d’elles, avant que la boucle ne progresse — tandis que les macrotâches sont traitées strictement une par une, avec un contrôle complet de la pile et de la file d’attente des microtâches entre chaque traitement.

    Décortiquons un casse-tête d’entretien bien connu

    Avec ces mécanismes clarifiés, il est utile d’analyser un extrait qui trompe de nombreux développeurs :

    console.log("Start");
    
    setTimeout(() => {
      console.log("Timeout Callback");
    }, 0);
    
    Promise.resolve().then(() => {
      console.log("Promise Callback");
    });
    
    console.log("End");
    

    En suivant pas à pas ces quatre composants, on comprend mieux le résultat surprenant obtenu.

    Pas synchrone en premier

    console.log("Start") est mis sur la pile, affiche "Start", puis retiré de celle-ci.

    Ensuite, setTimeout(...) est ajouté à la pile. Il indique à la couche Web API du navigateur de démarrer un compteur à zéro millisecondes avec la fonction de rappel fournie. Comme le délai est nul, ce compteur expire presque immédiatement en arrière-plan, ce qui amène le navigateur à placer directement () => console.log("Timeout Callback") dans la file d’attente des macro-tâches. L’appel à setTimeout lui-même est ensuite retiré de la pile.

    Après cela, Promise.resolve().then(...) est ajouté à la pile. La promesse se résout instantanément, et son gestionnaire .then() — () => console.log("Promise Callback") — est placé directement dans la file d’attente des micro-tâches. Cet appel est également retiré de la pile.

    Finalement, console.log("End") est ajouté, affiche "End", puis retiré de la pile.

    Lorsque la pile est vide

    À ce stade, il ne reste plus rien dans la pile d’appels. Le cycle d’événements se réveille et examine ses deux files : la file des microtâches contient le callback de Promise, tandis que la file des macrotâches contient le callback de Timeout.

    Les microtâches sont traitées en premier

    Le cycle vérifie d’abord la file des microtâches. Il trouve le callback de Promise, le place sur la pile, et « Callback de Promise » est affiché. La pile se vide à nouveau, et maintenant la file des microtâches est également vide.

    Puis, une macrotâche

    Puisque plus de microtâches restent, le cycle se tourne enfin vers la file des macrotâches. Il sélectionne l’entrée la plus ancienne — le callback de Timeout —, le place sur la pile, affiche « Callback de Timeout », puis le retire.

    En résumé, la séquence d’affichage finale est la suivante :

    Start
    End
    Promise Callback
    Timeout Callback
    

    Même si le délai était fixé à 0 millisecondes et que cette instruction avait été écrite avant la promesse dans le code, c’est la fonction de rappel de la promesse qui s’est exécutée en premier. Cela confirme la règle selon laquelle les microtâches interviennent toujours avant les macrotâches, quel que soit le petit délai accordé au timer.

    Cela entraîne une mauvaise compréhension courante quant à ce qu’indique réellement une valeur de délai. De nombreux débutants pensent que setTimeout(fn, 1000) signifie « exécuter cette fonction exactement 1 000 millisecondes plus tard ». Ce n’est pas le cas. En réalité, cela garantit simplement que la fonction de rappel ne sera inscrite dans la file d’attente des tâches qu’après au moins 1 000 millisecondes écoulées. Une fois inscrite dans la file, elle doit encore attendre son tour jusqu’à ce que la pile d’appels soit vide et que toutes les microtâches en attente aient été traitées.

    Ainsi, si votre pile d’appels est occupée pendant cinq secondes en exécutant un tri intensif ou en traitant une image, une fonction de rappel setTimeout(fn, 100) restera inactivée dans la file d’attente pendant ces cinq secondes entières avant même que le cycle des événements ne puisse y jeter un coup d’œil. En bref, le délai de timeout représente un temps d’attente minimal, et non un moment de déclenchement garanti.

    Cette tendance à vider la file d’attente a un effet secondaire plus dangereux qui mérite d’être compris séparément. Comme le cycle des événements refuse de passer à autre chose tant que la file des microtâches n’est pas complètement vide, il est possible de bloquer votre application sans pour autant bloquer techniquement la pile d’appels synchrone. Ce mode de défaillance est connu sous le nom de famine des microtâches, parfois appelée famine de la file d’attente.

    Imaginez une fonction comme celle-ci :

    function infiniteMicrotasks() {
      Promise.resolve().then(() => {
        infiniteMicrotasks();
      });
    }
    
    infiniteMicrotasks();
    

    Décortiquons ce qui se passe : l’appel initial est terminé et la pile est vidée. Le cycle d’événements vérifie ensuite la file des micro-tâches et en trouve une. Il exécute cette tâche, mais son exécution provoque immédiatement la planification d’une autre micro-tâche. Le cycle d’événements vérifie à nouveau la file, constate qu’elle est toujours occupée, et exécute la suivante — ce qui entraîne à son tour la planification d’une autre micro-tâche. La pile continue de se vider après chaque micro-tâche, mais le cycle d’événements ne sort jamais de cette étape de vérification. Il n’avançe jamais suffisamment pour permettre au navigateur de redessiner l’interface utilisateur ou d’accéder aux éléments en attente dans la file des macro-tâches.

    En pratique, la page semble complètement figée. Les clics sont ignorés, les animations s’arrêtent, et finalement le navigateur peut afficher un avertissement indiquant qu’un script ne répond pas.

    Pour résumer la manière dont chaque type de tâche est planifié et priorisé au cours du temps d’exécution, il est utile de consulter une comparaison détaillée montrant où le code synchrone, les microtâches, les macrotâches et le rendu s’inscrivent dans ce cycle, comme illustré dans le diagramme ci-joint.

    Comprendre le fonctionnement du boucle d’événements n’est pas seulement une information utile pour les entretiens — cela influence directement la manière dont vous devez concevoir des applications réelles, et trois habitudes pratiques en découlent.

    Tout d’abord, décomposez les boucles synchrones coûteuses. Si vous devez traiter un tableau contenant, disons, 500 000 éléments dans le thread principal, évitez de le faire en une seule boucle for ininterrompue, car cela bloquera l’interface. Divisez le travail en tranches et restituez le contrôle au cycle d’événements entre ces tranches en utilisant setTimeout(chunkHandler, 0) ou requestIdleCallback(). Cela permet au navigateur de gérer les clics et de redessiner les frames entre les tranches.

    Ensuite, soyez précis quant à ce que async/await font réellement en arrière-plan. Il s’agit d’un sucre syntaxique superposé aux promesses : tout code écrit avant le premier await s’exécute de manière synchrone, tandis que tout ce qui suit un await est enveloppé dans une fonction de rappel de microtâche qui reste en pause jusqu’à ce que la promesse attendue soit résolue.

    Troisièmement, ne considérez jamais les compteurs temporels comme des chronomètres précis. Si vous développez un compteur intégré à une application ou un séquenceur audio, ne comptez pas sur setInterval ou setTimeout pour atteindre des valeurs exactes au millimètre près. La charge du système, le fait que les navigateurs limitent le fonctionnement des onglets en arrière-plan pour économiser de la batterie, ainsi que les congestions dans les files d’attente entraînent tous une déviation. Préférez plutôt mesurer le temps écoulé en vous référant à l’horloge système à l’aide de performance.now().

    Le fait que JavaScript s’exécute sur une seule thread n’est pas une limitation à contourner — c’est l’une des meilleures décisions de conception du langage. En combinant un moteur synchrone rapide avec un ensemble d’API en arrière-plan et une boucle d’événements strictement gérée, JavaScript crée l’illusion de pouvoir effectuer plusieurs tâches en même temps, tout en évitant les problèmes de concurrence, les blocages et les difficultés liées à la sécurité des threads que présente le véritable multithreading dans d’autres langages.

    Lorsque vous comprendrez et respecterez le fonctionnement des files d’attente, l’exécution de votre code ne sera plus une source de surprises.

    Lectures complémentaires