La boucle d'événements JavaScript

La boucle d'événement est l'un des aspects les plus importants à comprendre sur JavaScript. Cet article l'explique en termes simples

introduction

LeBoucle d'événementest l'un des aspects les plus importants à comprendre sur JavaScript.

J'ai programmé pendant des années avec JavaScript, mais je n'ai jamaispleinementcompris comment les choses fonctionnent sous le capot. C'est très bien de ne pas connaître ce concept en détail, mais comme d'habitude, il est utile de savoir comment cela fonctionne, et vous pourriez aussi être un peu curieux à ce stade.

Cet article vise à expliquer les détails internes de la façon dont JavaScript fonctionne avec un seul thread et comment il gère les fonctions asynchrones.

Votre code JavaScript fonctionne avec un seul thread. Il ne se passe qu'une seule chose à la fois.

Il s'agit d'une limitation qui est en fait très utile, car elle simplifie beaucoup la façon dont vous programmez sans vous soucier des problèmes de concurrence.

Vous devez juste faire attention à la façon dont vous écrivez votre code et éviter tout ce qui pourrait bloquer le thread, comme les appels réseau synchrones ou infiniboucles.

En général, dans la plupart des navigateurs, il existe une boucle d'événements pour chaque onglet du navigateur, pour isoler chaque processus et éviter une page Web avec des boucles infinies ou un traitement lourd pour bloquer tout votre navigateur.

L'environnement gère plusieurs boucles d'événements simultanées, pour gérer les appels d'API par exemple.Travailleurs Webs'exécutent également dans leur propre boucle d'événements.

Vous devez principalement vous inquiéter du fait quevotre codefonctionnera sur une seule boucle d'événement et écrira du code avec cette chose à l'esprit pour éviter de le bloquer.

Bloquer la boucle d'événements

Tout code JavaScript qui prend trop de temps pour renvoyer le contrôle à la boucle d'événements bloquera l'exécution de tout code JavaScript dans la page, bloquera même le thread d'interface utilisateur, et l'utilisateur ne pourra pas cliquer, faire défiler la page, etc.

Presque toutes les primitives d'E / S en JavaScript ne sont pas bloquantes. Requêtes réseau,Node.jsopérations sur le système de fichiers, etc. Le blocage est l'exception, et c'est pourquoi JavaScript est tellement basé sur les rappels, et plus récemment surpromessesetasynchroniser / attendre.

La pile d'appels

La pile d'appels est une file d'attente LIFO (Last In, First Out).

La boucle d'événements vérifie en permanencepile d'appelspour voir si une fonction doit être exécutée.

Ce faisant, il ajoute tout appel de fonction qu'il trouve à la pile d'appels et exécute chacun dans l'ordre.

Vous connaissez la trace de la pile d'erreurs que vous connaissez peut-être, dans le débogueur ou dans la console du navigateur? Le navigateur recherche les noms des fonctions dans la pile d'appels pour vous informer de la fonction à l'origine de l'appel en cours:

Exception call stack

Une explication simple de la boucle d'événements

Prenons un exemple:

j'utilisefoo,baretbazcommenoms aléatoires. Entrez n'importe quel type de nom pour les remplacer

const bar = () => console.log('bar')

const baz = () => console.log(‘baz’)

const foo = () => { console.log(‘foo’) bar() baz() }

foo()

Ce code imprime

foo
bar
baz

as expected.

When this code runs, first foo() is called. Inside foo() we first call bar(), then we call baz().

At this point the call stack looks like this:

Call stack first example

The event loop on every iteration looks if there’s something in the call stack, and executes it:

Execution order first example

until the call stack is empty.

Queuing function execution

The above example looks normal, there’s nothing special about it: JavaScript finds things to execute, runs them in order.

Let’s see how to defer a function until the stack is clear.

The use case of setTimeout(() => {}), 0) is to call a function, but execute it once every other function in the code has executed.

Take this example:

const bar = () => console.log('bar')

const baz = () => console.log(‘baz’)

const foo = () => { console.log(‘foo’) setTimeout(bar, 0) baz() }

foo()

This code prints, maybe surprisingly:

foo
baz
bar

When this code runs, first foo() is called. Inside foo() we first call setTimeout, passing bar as an argument, and we instruct it to run immediately as fast as it can, passing 0 as the timer. Then we call baz().

At this point the call stack looks like this:

Call stack second example

Here is the execution order for all the functions in our program:

Execution order second example

Why is this happening?

The Message Queue

When setTimeout() is called, the Browser or Node.js start the timer. Once the timer expires, in this case immediately as we put 0 as the timeout, the callback function is put in the Message Queue.

The Message Queue is also where user-initiated events like click or keyboard events, or fetch responses are queued before your code has the opportunity to react to them. Or also DOM events like onLoad.

The loop gives priority to the call stack, and it first processes everything it finds in the call stack, and once there’s nothing in there, it goes to pick up things in the message queue.

We don’t have to wait for functions like setTimeout, fetch or other things to do their own work, because they are provided by the browser, and they live on their own threads. For example, if you set the setTimeout timeout to 2 seconds, you don’t have to wait 2 seconds - the wait happens elsewhere.

ES6 Job Queue

ECMAScript 2015 introduced the concept of the Job Queue, which is used by Promises (also introduced in ES6/ES2015). It’s a way to execute the result of an async function as soon as possible, rather than being put at the end of the call stack.

Promises that resolve before the current function ends will be executed right after the current function.

I find nice the analogy of a rollercoaster ride at an amusement park: the message queue puts you at the back of the queue, behind all the other people, where you will have to wait for your turn, while the job queue is the fastpass ticket that lets you take another ride right after you finished the previous one.

Example:

const bar = () => console.log('bar')

const baz = () => console.log(‘baz’)

const foo = () => { console.log(‘foo’) setTimeout(bar, 0) new Promise((resolve, reject) => resolve(‘should be right after baz, before bar’) ).then(resolve => console.log(resolve)) baz() }

foo()

This prints

foo
baz
should be right after baz, before bar
bar

That’s a big difference between Promises (and Async/await, which is built on promises) and plain old asynchronous functions through setTimeout() or other platform APIs.


More js tutorials: