2
votes

JavaScript Event Loop dans l'exécution dans le désordre

J'essayais un exemple d'un livre pour confirmer le fonctionnement de la boucle d'événements JavaScript, voici le code

foo
baz
baz 
bar

Le fonctionnement de setTimeout ici (exécuté dans le désordre) est simple.

const baz = () => console.log("baz");
const bar = () => console.log("bar");

const foo = () => {
    console.log("foo");
    setTimeout(bar, 0);
    baz();
}
setTimeout(baz, 0); // this somehow runs before foo() is finished
foo();

Ce que je ne comprends pas, c'est la commande lorsque j'ai ajouté une ligne

foo
baz
bar 

La sortie est

const baz = () => console.log("baz");
const bar = () => console.log("bar");

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

Comment se fait-il que le deuxième setTimeout se rince avant que foo () ne soit terminé?


2 commentaires

Considérez la boucle d'événements comme une file d'attente, votre code insère quelque chose à faire plus tard avant d'appeler foo, appelez foo, foo ajoute quelque chose d'autre à la file d'attente, foo se termine, la boucle d'événements extrait de la file d'attente la première soumission


Ah, je vois que je comprends maintenant beaucoup de mercis


4 Réponses :


0
votes

Vous pouvez le voir clairement si vous séparez les opérations asynchrones du reste du code:

baz 
bar

Si nous extrayons les fonctions setTimeout , nous aurons:

setTimeout(baz, 0);
setTimeout(bar, 0);

qui enregistre les deux premières lignes:

foo
baz

Lors de la première boucle, les fonctions asynchrones définies dans setTimeout ont été ajoutées à la boucle suivante, nous devons donc les exécuter:

const baz = () => console.log("baz");
const bar = () => console.log("bar");

const foo = () => {
    console.log("foo");
    // setTimeout(bar, 0);
    baz();
}
// setTimeout(baz, 0); // this somehow runs before foo() is finished
foo();

Résultat dans les deux prochaines lignes du journal:

const baz = () => console.log("baz");
const bar = () => console.log("bar");

const foo = () => {
    console.log("foo");
    setTimeout(bar, 0);
    baz();
}
setTimeout(baz, 0); // this somehow runs before foo() is finished
foo();


0 commentaires

3
votes

Vous devez garder à l'esprit que tout le code synchrone s'exécutera en premier, puis le code asynchrone. ( setTimeout mettra toujours en file d'attente les actions asynchrones, même si elles sont définies avec un timeout de 0.)

Donc, dans cet esprit, l'ordre des événements est le suivant:

  • enregistrer un appel asynchrone à baz()
  • sortie synchrone foo
  • enregistrer un appel asynchrone à bar()
  • appel de manière synchrone baz() , sortie de baz

Sachant que la synchronisation fonctionne en premier, nous obtenons d'abord foo puis baz .

Ensuite, nos événements asynchrones s'exécutent, produisant tour à tour baz et bar .


0 commentaires

0
votes

Vous appelez la fonction setTimeout(baz, 0) , elle va à la pile d'appels, puis à la phase des minuteries dans la boucle d'événement, et attendez là.

Après avoir appelé la fonction foo() , qui insère dans la pile d'appels console.log("foo") , setTimeout(bar, 0) et baz() . Comme console.log("foo") est une opération synchrone, elle est exécutée immédiatement et vous voyez "foo" dans la sortie. setTimeout(bar, 0) passe à la phase des minuteries dans la boucle d'événements et attend. Ensuite, exécutez la fonction baz() , qui à son tour lance console.log("baz") , et vous voyez le "baz" dans la sortie. Après avoir exécuté les opérations synchrones, la pile d'appels est vide et l'attente des minuteries est terminée, commençant à exécuter les rappels à partir de setTimeouts .

setTimeout(baz, 0) -> baz -> console.log("baz") = "baz" dans l'otput

et après ça

setTimeout(bar, 0) -> bar -> console.log("bar") = "bar" dans l'otput


0 commentaires

4
votes

Voici une explication du point de vue de la boucle d'événements.

Vous pouvez visualiser la pile d'appels, qui est utilisée pour garder une trace de l'endroit où nous nous trouvons dans un programme à un moment donné. Lorsque vous appelez une fonction, nous la poussons sur la pile, et lorsque nous retournons / terminons une fonction, nous la sortons du haut de la pile. Au départ, la pile est vide.

Lorsque vous exécutez votre code pour la première fois, votre "script" principal sera poussé sur la pile, et sera sorti de la pile une fois que le script aura fini de s'exécuter:

"foo"
"baz"
"baz"
"bar"

nous définissons ensuite quelques fonctions baz , bar et foo , et foo par atteindre notre première invocation de fonction, setTimeout(baz, 0) , et ainsi, nous la poussons sur la pile:

Stack:
------
<EMPTY>

Task Queue: (Front <--- Back)
bar 

setTimeout() lance une API Web qui, après 0 ms, met en file d'attente votre rappel baz dans la file d'attente des tâches . Une fois que setTimeout a transmis son travail à l'API Web, son travail est terminé et il a donc terminé son travail et peut être retiré de la pile:

Stack:
------
<EMPTY>

Task Queue: (Front <--- Back)
baz, bar 

C'est le travail de la boucle d'événements de prendre des tâches de la file d'attente de tâches et de les pousser sur la pile lorsque la pile est vide . Actuellement, la pile n'est pas vide car nous sommes toujours dans le script principal, donc baz() ne s'est pas encore exécuté. La prochaine invocation de fonction que nous rencontrons est foo() , donc nous poussons ceci sur notre pile:

Stack:
------
- foo()
- Main()

Task Queue: (Front <--- Back)
baz, bar

Foo appelle ensuite la méthode log() l'objet console, qui est également poussée sur la pile:

Stack:
------
- log("foo")
- foo()
- Main()

Task Queue: (Front <--- Back)
baz

Cela enregistre "foo" et log() est sorti de la pile une fois qu'il a terminé son travail. Nous continuons ensuite à parcourir la fonction foo. Nous rencontrons maintenant un appel de fonction à setTimeout(bar, 0); . Ceci, tout comme le premier appel de fonction, pousse setTimeout(bar, 0) sur la pile. Cela fait tourner une API Web qui ajoute une bar à la file d'attente des tâches. setTimeout(bar, 0) est également terminé une fois qu'il a transmis son travail à l'api Web, il est donc également retiré de la pile (voir les deuxième et troisième schémas ascii pour ces étapes), nous laissant avec:

Stack:
------
- foo()
- Main()

Task Queue: (Front <--- Back)
baz

Enfin, nous arrivons à la dernière ligne de la fonction foo qui appelle baz() . Cela pousse baz() sur la pile d'appels, puis pousse le log("baz") vers le haut de la pile d'appels, qui enregistre "baz" . Jusqu'à présent, nous avons enregistré "foo" puis "baz". Une fois que baz a été consigné, log() est sorti de la pile, tout comme baz() terminé.

Une fois que la dernière ligne de foo() est terminée, nous retournons implicitement, sortant foo() de la pile, nous laissant avec Main() . Une fois que nous sommes revenus de foo, notre contrôle / exécution est retourné au script principal après où foo() été invoqué. Comme il n'y a plus de fonctions à appeler dans notre script, nous sortons Main() de la pile, nous laissant avec:

Stack:
------
- Main()

Task Queue: (Front <--- Back)
baz

Maintenant que la pile est vide, la boucle d'événements peut entrer et gérer baz et bar dans la file d'attente des tâches. Tout d'abord, il sort baz de la file d'attente et le pousse sur la pile, qui appelle ensuite log("baz") , en poussant le log sur la pile puis en enregistrant "baz" . Une fois le journal terminé, le log et le baz sont retirés de la pile en la laissant à nouveau vide:

Stack:
------
- setTimeout(baz, 0)
- Main()

Maintenant que la pile est à nouveau vide, la boucle d'événement prend la première tâche de la file d'attente (c'est-à-dire: bar ) et la pousse dans la pile. bar appelle alors log("bar") , qui ajoute le log("bar") à la pile, ainsi que les logs "bar" à la console. Une fois la journalisation terminée, log() et bar() sont tous deux sortis de la pile.

En conséquence, la sortie de vos journaux est imprimée dans l'ordre suivant (voir les journaux en gras ci-dessus):

Stack:
------
- Main()   // <-- indicates that we're in the main script

Quelques bonnes ressources sur la boucle d'événements et la pile d'appels peuvent être trouvées ici , ici et ici .


0 commentaires