Maison > Article > interface Web > Parlons de la boucle d'événements dans Node
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 !
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]
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
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 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.
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.
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.
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.
Bien que tous les éléments soient importants, le composant clé de la programmation asynchrone dans Node.js est 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.
让我们来概括一下代码在 Node 运行时中的执行方式。在执行代码时,位于图片左侧的 V8 引擎负责 JavaScript 代码的执行。该引擎包含一个内存堆(Memory heap)和一个调用栈(Call stack)。
每当声明变量或函数时,都会在堆上分配内存。执行代码时,函数就会被推入调用栈中。当函数返回时,它就从调用栈中弹出了。这是对栈数据结构的简单实现,最后添加的项是第一个被移除。在图片右侧,是负责处理异步方法的 libuv。
每当我们执行异步方法时,libuv 接管任务的执行。然后使用操作系统本地异步机制运行任务。如果本地机制不可用或不足,则利用其线程池来运行任务,并确保主线程不被阻塞。
首先,让我们来看一下同步代码执行。以下代码由三个控制台日志语句组成,依次记录“First”,“Second”和“Third”。我们按照运行时执行顺序来查看代码。
// index.js console.log("First"); console.log("Second"); console.log("Third");
以下是 Node 运行时执行同步代码的可视化展示。
执行的主线程始终从全局作用域开始。全局函数(如果我们可以这样称呼它)被推入堆栈中。然后,在第 1 行,我们有一个控制台日志语句。这个函数被推入堆栈中。假设这个发生在 1 毫秒时,“First” 被记录在控制台上。然后,这个函数从堆栈中弹出。
执行到第 2 行时。假设到第 2 毫秒了,log 函数再次被推入堆栈中。“Second”被记录在控制台上,并弹出该函数。
最后,执行到第 3 行了。第 3 毫秒时,log 函数被推入堆栈,“Third”将记录在控制台上,并弹出该函数。此时已经没有代码要执行,全局也被弹出。
接下来,让我们看一下异步代码执行。有以下代码片段:包含三个日志语句,但这次第二个日志语句传递给了fs.readFile()
作为回调函数。
执行的主线程始终从全局作用域开始。全局函数被推入堆栈。然后执行到第 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 用于处理 Node.js 中的异步操作。对于像处理网络请求这样的异步操作,libuv 依赖于操作系统原生机制。对于没有本地 OS 支持的异步读取文件的操作,libuv 则依赖其线程池以确保主线程不被阻塞。然而,这也引发了一些问题。
setTimeout
和 setInterval
这类延迟执行回调函数的方法又是何时执行回调函数呢?setTimeout
和 readFile
这类异步任务同时完成,Node 如何决定哪个回调函数先在调用栈上运行?其中一个会有更多的优先级吗?所有这些问题都可以通过理解 libuv 核心部分——事件循环来得到答案。
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.
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.
setTimeout
et setInterval
. setTimeout
和 setInterval
相关的回调函数。fs
和 http
模块中提供的相关方法。setImmediate
函数相关的回调函数,这是特定于Node 的功能。最后,有两个不同队列组成微任务队列(microtask queue)。
process.nextTick
函数关联的回调函数。Promise
相关联的回调函数。需要注意的是计时器、I/O、检查和关闭队列都属于 libuv。然而,两个微任务队列并不属于 libuv。尽管如此,它们仍然是 Node 运行时环境中扮演着重要角色,并且在执行回调顺序方面发挥着重要作用。说到这里, 让我们来理解一下事件循环是如何工作的。
图中箭头是一个提示,但可能还不太容易理解。让我来解释一下队列的优先级顺序。首先要知道,所有用户编写的同步 JavaScript 代码都比异步代码优先级更高。这表示只有在调用堆栈为空时,事件循环才会发挥作用。
在事件循环中,执行顺序遵循某些规则。需要掌握的规则还是有一些的,我们逐个的了解一下:
此时,如果还有更多的回调需要处理,那么事件循环再运行一次(译注:事件循环在程序运行期间一直在运行,在当前没有可供处理的任务情况下,会处于等待状态,一旦有新任务就会执行),并重复相同的步骤。另一方面,如果所有回调都已执行并且没有更多代码要处理(译注:也就是程序执行结束),则事件循环退出。
这就是 libuv 事件循环在 Node.js 中执行异步代码的作用。有了这些规则,我们可以重新审视之前提出的问题。
当一个异步任务在 libuv 中完成时,什么时候 Node 会在调用栈上运行相关联的回调函数?
答案:只有当调用栈为空时才执行回调函数。
Node 是否会等待调用栈为空后再运行回调函数?还是打断正常执行流来运行回调函数?
答案:运行回调函数时不会打断正常执行流。
像 setTimeout
和 setInterval
Deuxiè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.
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. 🎜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). setTimeout
和 setInterval
的所有回调函数中第一优先级执行的(不考虑微任务队列)。
如果两个异步任务(例如 setTimeout
和 readFile
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 ?
Nous 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.
Conclusion
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!