aboutsummaryrefslogtreecommitdiff
path: root/files/fr/web/javascript/eventloop/index.html
blob: 9924edececc75397dfdd577c4fa00d26fb20df92 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
---
title: Gestion de la concurrence et boucle des événements
slug: Web/JavaScript/Concurrence_et_boucle_des_événements
tags:
  - Avancé
  - Guide
  - JavaScript
translation_of: Web/JavaScript/EventLoop
---
<div>{{jsSidebar("Advanced")}}</div>

<p>JavaScript gère la concurrence grâce à une « boucle d'événements ». Ce modèle est différent de la gestion faite par des langages comme C ou Java.</p>

<h2 id="Notions_liées_à_lexécution">Notions liées à l'exécution</h2>

<p>Les sections qui suivent décrivent un modèle théorique. En réalité, les moteurs JavaScript implémentent et optimisent fortement la sémantique décrite ici.</p>

<h3 id="Représentation_visuelle">Représentation visuelle</h3>

<p style="text-align: center;"><img alt="Stack, heap, queue" src="/files/4617/default.svg" style="height: 270px; width: 294px;"></p>

<h3 id="La_pile_dappels_stack">La pile d'appels (<em>stack</em>)</h3>

<p>Les appels de fonction forment une pile de cadre (<em>frames</em>).</p>

<pre class="brush: js notranslate">function f(b){
  var a = 12;
  return a + b + 35;
}

function g(x){
  var m = 4;
  return f(m * x);
}

console.log(g(21)); // affichera 131
</pre>

<p>Lors de l'appel à <code>g</code>, on crée un premier cadre contenant les arguments de <code>g</code> ainsi que les variables locales. Quand <code>g</code> appelle <code>f</code>, un deuxième cadre est créé et placé sur le premier et contient les arguments de <code>f</code> ainsi que les variables locales. Lorsque <code>f</code> a fini et renvoyé son résultat, le cadre correspondant (celui qui est sur le dessus) est retiré de la pile (il reste donc le cadre lié à l'appel de <code>g</code>). Quand <code>g</code> a fini grâce aux informations transmises, la pile devient vide.</p>

<h3 id="Le_tas_heap">Le tas (<em>heap</em>)</h3>

<p>Les objets sont alloués en mémoire dans un tas qui désigne une zone de la mémoire sans structure particulière.</p>

<h3 id="La_file_queue">La file (<em>queue</em>)</h3>

<p>Un environnement d'exécution JavaScript (<em>runtime</em>) contient une queue de messages à traiter. Chaque message est associé à une fonction. Lorsque la pile est vide ou a suffisamment d'espace, on retire un message de la queue et on le traite. Le traitement consiste à appeler la fonction associée au message (et donc à créer le cadre dans la pile d'appel). Le traitement d'un message est fini lorsque la pile d'appels redevient vide.</p>

<h2 id="La_boucle_dévénements">La boucle d'événements</h2>

<p>La boucle d'événement tire principalement son nom de son implémentation. Celle-ci ressemble à :</p>

<pre class="brush: js notranslate">while (queue.attendreMessage()){
  queue.traiterProchainMessage();
}</pre>

<p><code>queue.attendreMessage</code> est un fonction synchrone qui attend un message même s'il n'y en a aucun à traiter.</p>

<h3 id="Traiter_de_A_à_Z_run-to-completion">Traiter de A à Z (<em>run-to-completion</em>)</h3>

<p>Chaque message sera traité complètement avant tout autre message. Cela permet de savoir que, lorsqu'une fonction s'exécute, on ne peut pas observer l'exécution d'un autre code qui prendrait le pas (modifiant les données de la fonction par exemple). Le modèle de <em>thread</em> utilisé par le langage C, par exemple, que la fonction puisse être interrompue à tout moment pour permettre à un autre <em>thread</em> d'exécuter un autre code.</p>

<p>Ce modèle possède un désavantage : lorsqu'un message prend trop de temps à être traité, l'application ne peut plus gérer les interactions utilisateur comme les clics ou le défilement. Généralement, le navigateur affiche alors « Le script met trop de temps à répondre ». Une bonne pratique consiste à rendre le traîtement de message le plus court possible et à découper le message problématique en plusieurs messages.</p>

<h3 id="Lajout_de_messages">L'ajout de messages</h3>

<p>Dans les navigateurs web, des messages sont ajoutés à chaque fois qu'un événement se produit et qu'un gestionnaire d'événements y est attaché. S'il n'y a pas d'écouteur (<em>listener</em>) pour intercepter l'événement, il est perdu. Ainsi, si on clique un élément qui possède un gestionnaire d'événements pour les clics, un message sera ajouté (il en va de même avec les autres événements).</p>

<p>La fonction <code><a href="/fr/docs/DOM/window.setTimeout">setTimeout</a></code> est appelée avec deux arguments : un message à la suite de la queue et la durée à attendre (optionnelle, par défaut elle vaut 0). La durée représente le temps minimal à attendre avant que le message soit placé dans la queue. S'il n'y a pas d'autre message dans la queue, le message sera traîté directement. En revanche, s'il y a d'autres messages auparavant, le message de <code>setTimeout</code> devra attendre la fin du traîtement des messages précédents déjà présents dans la queue. C'est pourquoi le deuxième argument de cette fonction indique une durée minimum et non une durée garantie.</p>

<div class="warning">
<p><strong>Attention :</strong> L'argument passé pour le délai à <code>setTimeout</code> ne correspond pas au temps exact. Cela correspond au délai minimum et non à un délai garanti. Par exemple <code>setTimeout(maFonction(),100);</code> indique uniquement que <code>maFonction</code> sera lancé <strong>au moins</strong> après 100 millisecondes.</p>
</div>

<p>Voici un exemple qui illustre ce concept (<code>setTimeout</code> ne s'exécute pas immédiatement après la fin du <em>timer</em>) :</p>

<pre class="brush: js notranslate">const s = new Date().getSeconds();

setTimeout(function() {
  // prints
  console.log("Exécuté après " + (new Date().getSeconds() - s) + " secondes.");
}, 500);

while(true) {
  if(new Date().getSeconds() - s &gt;= 2) {
    console.log("Ouf, on a bouclé pendant 2 secondes");
    break;
  }
}
</pre>

<h3 id="Zéro_délai">Zéro délai</h3>

<p>Un délai à zéro ne signifie pas que le callback sera déclenché après zéro milliseconde. Appeler <code><a href="https://developer.mozilla.org/fr/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout" title="The documentation about this has not yet been written; please consider contributing!">setTimeout</a></code> avec un délai de <code>0</code> (zéro) milliseconde n'éxécute pas le callback après l'interval donné.</p>

<p>L'exécution dépend du nombre de taches en attente dans la queue. Dans l'exemple ci-dessous, le message <code>'ceci est juste un message'</code> sera affiché dans la console avant que le message dans le callback soit traité, parce que le délai est le temps <em>minimum</em> requis par l'environnement d'exécution (runtime) pour traiter la demande (pas un temps <em>garanti</em>).</p>

<p>Fondamentalement, <code>setTimeout</code> doit attendre la fin de tout le code pour les messages en file d'attente, même si vous avez spécifié une limite de temps particulière pour votre setTimeout.</p>

<pre class="brush: js notranslate">(function() {

  console.log('ceci est le début');

  setTimeout(function cb() {
    console.log('Callback 1: ceci est un msg depuis le callback');
  }); // has a default time value of 0

  console.log('ceci est juste un message');

  setTimeout(function cb1() {
    console.log('Callback 2: ceci est un msg depuis le callback');
  }, 0);

  console.log('ceci est la fin');

})();

// "ceci est le début"
// "ceci est juste un message"
// "ceci est la fin"
// "Callback 1: ceci est un msg depuis le callback"
// "Callback 2: ceci est un msg depuis le callback"</pre>

<h3 id="La_communication_entre_plusieurs_environnements_dexécution">La communication entre plusieurs environnements d'exécution</h3>

<p>Un web worker ou une <code>iframe</code> d'origine multiple (<em>cross origin</em>) possède sa propre pile, son propre tas et sa propre queue de messages. Deux environnements d'exécution distincts peuvent uniquement communiquer via des messages envoyés par la méthode <a href="/fr/docs/Web/API/window.postMessage"><code>postMessage</code></a>. Cette méthode permet d'ajouter un message à un autre environnement d'exécution si celui-ci « écoute » les événements <code>message</code>.</p>

<h2 id="Non_bloquant">Non bloquant</h2>

<p>Le modèle de la boucle d'événement possède une caractéristique intéressante : JavaScript, à  la différence d'autres langages, ne bloque jamais. La gestion des entrées-sorties (<em>I/O</em>) est généralement traitée par des événements et des callbacks. Ainsi, quand l'application attend le résultat d'une requête <a href="/fr/docs/IndexedDB">IndexedDB</a> ou d'une requête <a href="/fr/docs/XMLHttpRequest">XHR</a>, il reste possible de traiter d'autres éléments comme les saisies utilisateur.</p>

<p>Il existe certaines exceptions historiques comme <code>alert</code> ou des appels XHR synchrones. C'est une bonne pratique que de les éviter. Attention, <a href="https://stackoverflow.com/questions/2734025/is-javascript-guaranteed-to-be-single-threaded/2734311#2734311">certaines exceptions existent</a> (mais relèvent généralement de bugs d'implémentation).</p>

<h2 id="Spécifications">Spécifications</h2>

<table class="standard-table">
 <tbody>
  <tr>
   <th scope="col">Spécification</th>
   <th scope="col">É tat</th>
   <th scope="col">Commentaires</th>
  </tr>
  <tr>
   <td>{{SpecName('HTML WHATWG', 'webappapis.html#event-loops', 'Event loops')}}</td>
   <td>{{Spec2('HTML WHATWG')}}</td>
   <td></td>
  </tr>
  <tr>
   <td><a href="https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/#what-is-the-event-loop">Boucle d'évènements pour Node.js</a></td>
   <td>Standard évolutif</td>
   <td></td>
  </tr>
 </tbody>
</table>