Maison >interface Web >js tutoriel >Parlons de la boucle d'événements dans Node

Parlons de la boucle d'événements dans Node

青灯夜游
青灯夜游avant
2023-04-11 19:08:211601parcourir

La boucle d'événements est un élément fondamental de Node.js. Elle permet une programmation asynchrone en garantissant que le thread principal n'est pas bloqué. Comprendre la boucle d'événements est crucial pour créer des applications efficaces. L'article suivant vous donnera une compréhension approfondie de la boucle d'événements dans Node. J'espère qu'il vous sera utile !

Parlons de la boucle dévénements dans Node

Vous utilisez Node.js depuis un certain temps, avez créé quelques applications, essayé différents modules et vous êtes même à l'aise avec la programmation asynchrone. Mais quelque chose continue de vous déranger : la boucle des événements.

Si vous êtes comme moi, vous avez passé d'innombrables heures à lire de la documentation et à regarder des vidéos pour essayer de comprendre la boucle de l'événement. Mais même en tant que développeur expérimenté, vous aurez peut-être du mal à comprendre pleinement son fonctionnement. C'est pourquoi j'ai préparé ce guide visuel pour vous aider à bien comprendre la boucle d'événements Node.js. Alors asseyez-vous, prenez une tasse de café et plongeons dans le monde de la boucle d'événements Node.js. [Recommandations de didacticiels connexes : Tutoriel vidéo Nodejs, Enseignement de la programmation]

Programmation asynchrone en JavaScript

Nous commencerons par un examen de la programmation asynchrone en JavaScript. Bien que JavaScript soit utilisé dans les applications Web, mobiles et de bureau, il est important de se rappeler que, à la base, JavaScript est un langage synchrone, bloquant et à thread unique . Comprenons cette phrase à travers un court extrait de code.

// index.js

function A() {
  console.log("A");
}

function B() {
  console.log("B");
}

A()
B()

// Logs A and then B

JavaScript est synchrone

Si nous avons deux fonctions qui enregistrent les messages sur la console, le code sera exécuté de haut en bas, une ligne à la fois. Dans l'extrait de code ci-dessus, nous voyons que A est enregistré avant B.

JavaScript bloque

JavaScript bloque en raison de sa nature synchrone. Quelle que soit la durée du processus précédent, les processus suivants ne démarreront pas tant que le premier ne sera pas terminé. Dans l'extrait de code, si la fonction A doit exécuter un gros bloc de code, JavaScript doit terminer cette opération sans se connecter à la fonction B. Même si ce bout de code prend 10 secondes voire une minute.

Vous avez peut-être rencontré cette situation dans votre navigateur. Lorsqu'une application Web s'exécute dans le navigateur et exécute des blocs de code intensifs sans rendre le contrôle au navigateur, celui-ci peut se bloquer, ce que l'on appelle un blocage. Le navigateur ne peut pas continuer à traiter les entrées de l'utilisateur et à effectuer d'autres tâches jusqu'à ce que l'application Web rende le contrôle du processeur au navigateur.

JavaScript est monothread

Un thread est le processus que votre programme JavaScript peut utiliser pour exécuter des tâches. Chaque thread ne peut effectuer qu'une seule tâche à la fois. Contrairement à d'autres langages qui prennent en charge le multithreading et peuvent exécuter plusieurs tâches simultanément, JavaScript n'a qu'un seul thread appelé thread principal qui exécute le code.

En attente de JavaScript

Comme vous pouvez l'imaginer, ce modèle JavaScript crée des problèmes car nous devons attendre que les données soient récupérées avant de pouvoir continuer à exécuter le code. Cette attente peut prendre plusieurs secondes, pendant lesquelles nous ne pouvons exécuter aucun autre code. Si JavaScript continue le traitement sans attendre, une erreur se produit. Nous devons implémenter un comportement asynchrone en JavaScript. Allons dans Node.js et jetons un coup d'œil.

Node.js Runtime

Parlons de la boucle dévénements dans Node

Le runtime Node.js est un environnement qui vous permet d'utiliser et d'exécuter des programmes JavaScript sans utiliser de navigateur. Core - le runtime Node, se compose de trois composants principaux.

  • Les dépendances externes - telles que V8, libuv, crypto, etc. - sont des fonctionnalités requises de Node.js
  • Les fonctionnalités C++ fournissent des fonctionnalités telles que l'accès au système de fichiers et la mise en réseau.
  • La bibliothèque JavaScript fournit des fonctions et des outils pour appeler facilement des fonctionnalités C++ à l'aide de code JavaScript.

Bien que tous les éléments soient importants, le composant clé de la programmation asynchrone dans Node.js est libuv.

Libuv

Libuv est une bibliothèque open source multiplateforme écrite en langage C. Dans le runtime Node.js, son rôle est de fournir un support pour la gestion des opérations asynchrones. Voyons comment cela fonctionne.

Exécution de code dans le runtime Node.js

Parlons de la boucle dévénements dans Node

让我们来概括一下代码在 Node 运行时中的执行方式。在执行代码时,位于图片左侧的 V8 引擎负责 JavaScript 代码的执行。该引擎包含一个内存堆(Memory heap)和一个调用栈(Call stack)。

每当声明变量或函数时,都会在堆上分配内存。执行代码时,函数就会被推入调用栈中。当函数返回时,它就从调用栈中弹出了。这是对栈数据结构的简单实现,最后添加的项是第一个被移除。在图片右侧,是负责处理异步方法的 libuv。

每当我们执行异步方法时,libuv 接管任务的执行。然后使用操作系统本地异步机制运行任务。如果本地机制不可用或不足,则利用其线程池来运行任务,并确保主线程不被阻塞。

同步代码执行

首先,让我们来看一下同步代码执行。以下代码由三个控制台日志语句组成,依次记录“First”,“Second”和“Third”。我们按照运行时执行顺序来查看代码。

// index.js
console.log("First");
console.log("Second");
console.log("Third");

以下是 Node 运行时执行同步代码的可视化展示。

Parlons de la boucle dévénements dans Node

执行的主线程始终从全局作用域开始。全局函数(如果我们可以这样称呼它)被推入堆栈中。然后,在第 1 行,我们有一个控制台日志语句。这个函数被推入堆栈中。假设这个发生在 1 毫秒时,“First” 被记录在控制台上。然后,这个函数从堆栈中弹出。

执行到第 2 行时。假设到第 2 毫秒了,log 函数再次被推入堆栈中。“Second”被记录在控制台上,并弹出该函数。

最后,执行到第 3 行了。第 3 毫秒时,log 函数被推入堆栈,“Third”将记录在控制台上,并弹出该函数。此时已经没有代码要执行,全局也被弹出。

异步代码执行

接下来,让我们看一下异步代码执行。有以下代码片段:包含三个日志语句,但这次第二个日志语句传递给了fs.readFile() 作为回调函数。

Parlons de la boucle dévénements dans Node

执行的主线程始终从全局作用域开始。全局函数被推入堆栈。然后执行到第 1 行,在第 1 毫秒时,“First”被记录在控制台中,并弹出该函数。然后执行移动到第 2 行,在第 2毫秒时,readFile 方法被推入堆栈。由于 readFile 是异步操作,因此它会转移(off-loaded)到 libuv。

JavaScript 从调用堆栈中弹出了 readFile 方法,因为就第 2 行的执行而言,它的工作已经完成了。在后台,libuv 开始在单独的线程上读取文件内容。在第 3 毫秒时,JavaScript 继续进行到第 5 行,将 log 函数推入堆栈,“Third”被记录到控制台中,并将该函数弹出堆栈。

大约在第 4 毫秒左右,假设文件读取任务已经完成,则相关回调函数现在会在调用栈上执行, 在回调函数内部遇到 log 函数。

log 函数推入到到调用栈,“Second”被记录到控制台并弹出 log 函数 。由于回调函数中没有更多要执行的语句,因此也被弹出 。没有更多代码可运行了 ,所以全局函数也从堆栈中删除 。

控制台输出“First”,“Third”,然后是“Second”。

Libuv 和异步操作

很明显,libuv 用于处理 Node.js 中的异步操作。对于像处理网络请求这样的异步操作,libuv 依赖于操作系统原生机制。对于没有本地 OS 支持的异步读取文件的操作,libuv 则依赖其线程池以确保主线程不被阻塞。然而,这也引发了一些问题。

  • 当一个异步任务在 libuv 中完成时,什么时候 Node 会在调用栈上运行相关联的回调函数?
  • Node 是否会等待调用栈为空后再运行回调函数?还是打断正常执行流来运行回调函数?
  • setTimeoutsetInterval 这类延迟执行回调函数的方法又是何时执行回调函数呢?
  • 如果 setTimeoutreadFile 这类异步任务同时完成,Node 如何决定哪个回调函数先在调用栈上运行?其中一个会有更多的优先级吗?

所有这些问题都可以通过理解 libuv 核心部分——事件循环来得到答案。

Qu'est-ce que la boucle d'événement ?

Techniquement parlant, la boucle d'événements n'est qu'un programme en langage C. Mais dans Node.js, vous pouvez le considérer comme un modèle de conception permettant de coordonner l'exécution de code synchrone et asynchrone.

Boucle d'événements visuels

La boucle d'événements est une boucle qui s'exécute tant que votre application Node.js est en cours d'exécution. Il existe six files d'attente différentes dans chaque boucle, chacune contenant une ou plusieurs fonctions de rappel qui doivent éventuellement être exécutées sur la pile d'appels.

Parlons de la boucle dévénements dans Node

  • Tout d'abord, il existe une file d'attente de minuterie (techniquement appelée min-heap), qui est stockée en relation avec la fonction de rappel setTimeout et setInterval.
  • setTimeoutsetInterval 相关的回调函数。
  • 其次,有一个 I/O 队列(I/O queue),其中包含与所有异步方法相关的回调函数,例如 fshttp 模块中提供的相关方法。
  • 第三个是检查队列(check queue),它保存与 setImmediate 函数相关的回调函数,这是特定于Node 的功能。
  • 第四个是关闭队列(close queue),它保存与异步任务关闭事件相关联的回调函数。

最后,有两个不同队列组成微任务队列(microtask queue)。

  • nextTick 队列保存了与 process.nextTick 函数关联的回调函数。
  • Promise 队列则保存了JavaScript 中本地 Promise 相关联的回调函数。

需要注意的是计时器、I/O、检查和关闭队列都属于 libuv。然而,两个微任务队列并不属于 libuv。尽管如此,它们仍然是 Node 运行时环境中扮演着重要角色,并且在执行回调顺序方面发挥着重要作用。说到这里, 让我们来理解一下事件循环是如何工作的。

事件循环是如何工作的?

图中箭头是一个提示,但可能还不太容易理解。让我来解释一下队列的优先级顺序。首先要知道,所有用户编写的同步 JavaScript 代码都比异步代码优先级更高。这表示只有在调用堆栈为空时,事件循环才会发挥作用。

在事件循环中,执行顺序遵循某些规则。需要掌握的规则还是有一些的,我们逐个的了解一下:

  1. 执行微任务队列(microtask queue)中的所有回调函数。首先是 nextTick 队列中的任务,然后是 Promise 队列中的任务。
  2. 执行计时器队列(timer queue)内的所有回调函数。
  3. 如果微任务队列中存在回调函数,则在计时器队列内每执行完一次回调函数之后执行微任务队列中的所有回调函数。首先是 nextTick 队列中的任务,然后是 Promise 队列中的任务。
  4. 执行 I/O 队列(I/O queue)内的所有回调函数。
  5. 如果微任务队列中存在回调函数,按照先 nextTick 队列后 Promise 队列的顺序依次执行微任务队列中的所有回调函数。
  6. 执行检查队列(check queue)内的所有回调函数。
  7. 如果微任务队列中存在回调函数,则在检查队列内每个回调之后执行微任务队列中的所有回调函数 。首先是 nextTick 队列中的任务,然后是 Promise 队列中的任务。
  8. 执行关闭队列(close queue)内的所有回调函数。
  9. 在同一循环的最后,再执行一次微任务队列。首先是 nextTick 队列中的任务,然后是 Promise 队列中的任务。

此时,如果还有更多的回调需要处理,那么事件循环再运行一次(译注:事件循环在程序运行期间一直在运行,在当前没有可供处理的任务情况下,会处于等待状态,一旦有新任务就会执行),并重复相同的步骤。另一方面,如果所有回调都已执行并且没有更多代码要处理(译注:也就是程序执行结束),则事件循环退出。

这就是 libuv 事件循环在 Node.js 中执行异步代码的作用。有了这些规则,我们可以重新审视之前提出的问题。


当一个异步任务在 libuv 中完成时,什么时候 Node 会在调用栈上运行相关联的回调函数?

答案:只有当调用栈为空时才执行回调函数。

Node 是否会等待调用栈为空后再运行回调函数?还是打断正常执行流来运行回调函数?

答案:运行回调函数时不会打断正常执行流。

setTimeoutsetIntervalDeuxièmement, il existe une file d'attente d'E/S (I/O queue), qui contient des fonctions de rappel liées à toutes les méthodes asynchrones, telles que fs et http modules Méthodes associées fournies dans . La troisième est la file d'attente de vérification, qui contient la fonction de rappel liée à la fonction setImmediate, qui est une fonction spécifique au nœud.

Le quatrième est la file d'attente de fermeture, qui enregistre la fonction de rappel associée à l'événement de fermeture de tâche asynchrone. 🎜Enfin, il existe deux files d'attente différentes formant la file d'attente des microtâches. La file d'attente 🎜🎜🎜nextTick enregistre la fonction de rappel associée à la fonction process.nextTick. 🎜La file d'attente Promise stocke les fonctions de rappel associées à la Promise locale en JavaScript. 🎜Il convient de noter que les minuteries, les E/S, les files d'attente de vérification et de fermeture appartiennent tous à libuv. Cependant, les deux files d'attente de microtâches n'appartiennent pas à libuv. Néanmoins, ils jouent toujours un rôle important dans l'environnement d'exécution de Node et jouent un rôle important dans l'ordre dans lequel les rappels sont exécutés. Cela dit, comprenons comment fonctionne la boucle d’événements. 🎜

🎜Comment fonctionne la boucle d'événements ? 🎜🎜🎜La flèche sur l'image est un indice, mais elle n'est peut-être pas facile à comprendre. Laissez-moi vous expliquer l'ordre de priorité de la file d'attente. La première chose à savoir est que tout le code JavaScript synchrone écrit par l’utilisateur a priorité sur le code asynchrone. Cela signifie que la boucle d'événements ne fonctionne que lorsque la pile d'appels est vide. 🎜🎜Dans la boucle événementielle, l'ordre d'exécution suit certaines règles. Il reste encore quelques règles à maîtriser. Examinons-les une par une : 🎜
    🎜Exécutez toutes les fonctions de rappel dans la file d'attente des microtâches. D’abord les tâches de la file d’attente nextTick, puis les tâches de la file d’attente Promise. 🎜Exécutez toutes les fonctions de rappel dans la file d'attente du minuteur. 🎜S'il y a une fonction de rappel dans la file d'attente des microtâches, toutes les fonctions de rappel dans la file d'attente des microtâches seront exécutées après l'exécution de chaque fonction de rappel dans la file d'attente du minuteur🎜. D’abord les tâches de la file d’attente nextTick, puis les tâches de la file d’attente Promise. 🎜Exécutez toutes les fonctions de rappel dans la file d'attente d'E/S. 🎜S'il y a des fonctions de rappel dans la file d'attente des microtâches, toutes les fonctions de rappel dans la file d'attente des microtâches seront exécutées séquentiellement dans l'ordre de la file d'attente nextTick puis de la file d'attente Promise. 🎜Exécutez toutes les fonctions de rappel dans la file d'attente de vérification. 🎜S'il y a une fonction de rappel dans la file d'attente des microtâches, toutes les fonctions de rappel dans la file d'attente des microtâches seront exécutées après 🎜vérification de chaque rappel dans la file d'attente. D’abord les tâches de la file d’attente nextTick, puis les tâches de la file d’attente Promise. 🎜Exécutez toutes les fonctions de rappel dans la file d'attente de fermeture. 🎜À la fin de la même boucle, exécutez à nouveau la file d'attente des microtâches. D’abord les tâches de la file d’attente nextTick, puis les tâches de la file d’attente Promise.
🎜À ce stade, s'il y a plus de rappels à traiter, la boucle d'événements s'exécutera à nouveau (Annotation : la boucle d'événements continue de s'exécuter pendant que le programme est en cours d'exécution, et il n'y a actuellement aucune tâche à traiter Ensuite, il sera en attente et sera exécuté dès qu'il y aura une nouvelle tâche), et répétez les mêmes étapes. En revanche, si tous les rappels ont été exécutés et qu'il n'y a plus de code à traiter, la boucle d'événements se termine. 🎜🎜C'est ce que fait la boucle d'événements libuv pour exécuter du code asynchrone dans Node.js. Avec ces règles en main, nous pouvons revenir sur les questions que nous avons posées plus tôt. 🎜
🎜🎜Lorsqu'une tâche asynchrone est terminée dans libuv, quand Node exécutera-t-il la fonction de rappel associée sur la pile d'appels ? 🎜🎜🎜Réponse : La fonction de rappel n'est exécutée que lorsque la pile d'appels est vide. 🎜🎜🎜 Node attendra-t-il que la pile d'appels soit vide avant d'exécuter la fonction de rappel ? Ou interrompre le flux d'exécution normal pour exécuter la fonction de rappel ? 🎜🎜🎜Réponse :L'exécution d'une fonction de rappel n'interrompt pas le flux normal d'exécution. 🎜🎜🎜Méthodes comme setTimeout et setInterval qui retardent l'exécution des fonctions de rappel. Quand la fonction de rappel sera-t-elle exécutée ? 🎜🎜

Réponse : setTimeout et setInterval sont exécutés avec la première priorité parmi toutes les fonctions de rappel (quelle que soit la file d'attente des microtâches). setTimeoutsetInterval 的所有回调函数中第一优先级执行的(不考虑微任务队列)。

如果两个异步任务(例如 setTimeoutreadFile

Si deux tâches asynchrones (telles que setTimeout et readFile) se terminent en même temps, comment Node décide-t-il quelle fonction de rappel est exécutée en premier dans la pile d'appels ? L’un aura-t-il une priorité plus élevée que l’autre ?

Réponse :
En cas d'achèvement simultané, le rappel du timer sera exécuté avant le rappel des E/S.

Parlons de la boucle dévénements dans NodeNous avons beaucoup appris jusqu'à présent, mais j'espère que vous pourrez garder à l'esprit la séquence d'exécution présentée dans l'image ci-dessous, car elle montre complètement comment Node.js exécute du code asynchrone dans les coulisses.

Mais vous vous demandez peut-être : "Où est le code pour vérifier cette visualisation ?". Eh bien, chaque file d'attente dans la boucle d'événements a des nuances d'implémentation, nous ferions donc mieux d'en parler une par une. Cet article est le premier d'une série sur la boucle d'événements Node.js. Assurez-vous de consulter le lien à la fin de l'article pour comprendre les détails de l'opération dans chaque file d'attente. Même si vous avez une profonde impression en tête maintenant, vous risquez encore de tomber dans certains pièges lorsque vous arrivez au scénario spécifique. .

Conclusion

Ce guide visuel couvre les bases de la programmation asynchrone en JavaScript, le runtime Node.js et libuv, qui gère les opérations asynchrones. Grâce à ces connaissances, vous pouvez créer un modèle de boucle d'événements puissant qui sera utile lors de l'écriture de code tirant parti de la nature asynchrone de Node.js.

Pour plus de connaissances sur les nœuds, veuillez visiter : 🎜tutoriel Nodejs🎜 ! 🎜

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer