An article explaining the event loop in Node.js in detail
This article will give you a deep understanding of the event loop in Node. I hope it will be helpful to everyone!
ALL THE TIME, most of the javascript
code we write is compiled and run in the browser environment, so maybe we are not familiar with the browser The event loop mechanism of NodeJS is more in-depth than the event loop of Node.JS
. However, when I started to study NodeJS in depth recently, I found that the event loop mechanism of NodeJS is very different from the browser side. I hereby record it. Come and study it in depth to help yourself and your friends read and understand it after forgetting.
What is the event loop
First we need to understand some of the most basic things, such as this event loop, the event loop is Refers to Node.js performing non-blocking I/O operations. Although JavaScript is single-threaded, since most kernels are multi-threaded, Node.js
will try its best to Load operations into the system kernel. So they can handle multiple operations performed in the background. When one of the operations completes, the kernel tells Node.js
so that Node.js
can add the corresponding callback to the polling queue for final execution. [Related tutorial recommendations: nodejs video tutorial]
event loop
will be initialized when Node.js starts, and each event loop
will include Six cycle phases in the following order:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
- 1.
timers
Phase: This phase executessetTimeout(callback)
andsetInterval(callback)
Scheduled callback; - 2.
I/O callbacks
Phase: This phase executes certain Callbacks for system operations, such as types of TCP errors. For example, some *nix systems want to wait to report an error if a TCP socket receives ECONNREFUSED when trying to connect. This operation will wait for execution in the ==I/O callback phase==; - 3.
idle, prepare
Phase: only used internally by node; - 4.
poll
Phase : Obtain new I/O events, such as operations to read files, etc. Under appropriate conditions, the node will block Here; - 5.
check
Phase: Execute the callbacks set bysetImmediate()
; - 6.
close callbacks
Phase : For example, the callback ofsocket.on('close', callback)
will be executed at this stage;
Detailed explanation of the event loop
This picture is the operating principle of the entire Node.js, from left to right, from top to bottom, Node .js is divided into four layers, namely Application layer
, V8 engine layer
, Node API layer
and LIBUV layer
.
- Application layer: That is, the JavaScript interaction layer. The most common ones are Node.js modules, such as http, fs
- V8 engine layer: That is, using the V8 engine to parse JavaScript syntax, and then interact with the lower API
- NodeAPI layer: Provides system calls for the upper module, usually implemented in C language, to interact with the operating system.
- LIBUV layer: It is a cross-platform bottom-level encapsulation that implements event loops, file operations, etc., and is the core of Node.js's asynchronous implementation.
Detailed explanation of the content of each cycle stage
##timers
Phase
Specify a timer A lower limit time instead of an exact time, and the callback is executed after reaching this lower limit time. Timers will execute callbacks as early as possible after the specified time has passed, but system scheduling or the execution of other callbacks may delay them.
- Note: Technically, the poll phase controls when timers are executed.
- Note: This lower limit time has a range: [1, 2147483647]. If the set time is not within this range, it will be set to 1.
I/O callbacks
Phase
This phase performs callbacks for some system operations. For example, TCP errors, such as a TCP socket receiving ECONNREFUSED when trying to connect, Unix-like systems will wait to report errors, which will be queued in the I/O callbacks phase. The name may lead people to misunderstand that the I/O callback handler is executed. In fact, the I/O callback will be handled by the poll phase.
poll
阶段 poll 阶段有两个主要功能:(1)执行下限时间已经达到的timers的回调,(2)然后处理 poll 队列里的事件。 当event loop进入 poll 阶段,并且 没有设定的 timers(there are no timers scheduled),会发生下面两件事之一:
如果 poll 队列不空,event loop会遍历队列并同步执行回调,直到队列清空或执行的回调数到达系统上限;
如果 poll 队列为空,则发生以下两件事之一:
- 如果代码已经被setImmediate()设定了回调, event loop将结束 poll 阶段进入 check 阶段来执行 check 队列(里面的回调 callback)。
- 如果代码没有被setImmediate()设定回调,event loop将阻塞在该阶段等待回调被加入 poll 队列,并立即执行。
但是,当event loop进入 poll 阶段,并且 有设定的timers,一旦 poll 队列为空(poll 阶段空闲状态): event loop将检查timers,如果有1个或多个timers的下限时间已经到达,event loop将绕回 timers 阶段,并执行 timer 队列。
check
阶段 这个阶段允许在 poll 阶段结束后立即执行回调。如果 poll 阶段空闲,并且有被setImmediate()设定的回调,event loop会转到 check 阶段而不是继续等待。
setImmediate() 实际上是一个特殊的timer,跑在event loop中一个独立的阶段。它使用
libuv
的API 来设定在 poll 阶段结束后立即执行回调。通常上来讲,随着代码执行,event loop终将进入 poll 阶段,在这个阶段等待 incoming connection, request 等等。但是,只要有被setImmediate()设定了回调,一旦 poll 阶段空闲,那么程序将结束 poll 阶段并进入 check 阶段,而不是继续等待 poll 事件们 (poll events)。
close callbacks
阶段 如果一个 socket 或 handle 被突然关掉(比如 socket.destroy()),close事件将在这个阶段被触发,否则将通过process.nextTick()触发
这里呢,我们通过伪代码来说明一下,这个流程:
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 |
|
走进案例解析
我们来看一个简单的EventLoop
的例子:
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 |
|
这里呢,为了让这个setTimeout
优先于fs.readFile
回调, 执行了process.nextTick
, 表示在进入timers
阶段前, 等待20ms
后执行文件读取.
1. nextTick
与 setImmediate
process.nextTick
不属于事件循环的任何一个阶段,它属于该阶段与下阶段之间的过渡, 即本阶段执行结束, 进入下一个阶段前, 所要执行的回调。有给人一种插队的感觉.setImmediate
的回调处于check阶段, 当poll阶段的队列为空, 且check阶段的事件队列存在的时候,切换到check阶段执行,参考nodejs进阶视频讲解:进入学习
nextTick 递归的危害
由于nextTick具有插队的机制,nextTick的递归会让事件循环机制无法进入下一个阶段. 导致I/O处理完成或者定时任务超时后仍然无法执行, 导致了其它事件处理程序处于饥饿状态. 为了防止递归产生的问题, Node.js 提供了一个 process.maxTickDepth (默认 1000)。
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 |
|
此时永远无法跳到timer
阶段去执行setTimeout里面的回调方法
, 因为在进入timers
阶段前有不断的nextTick
插入执行. 除非执行了1000次到了执行上限,所以上面这个案例会不断地打印出nextTick
字符串
2. setImmediate
如果在一个I/O周期
内进行调度,setImmediate() 将始终在任何定时器(setTimeout、setInterval)之前执行.
3. setTimeout
与 setImmediate
- setImmediate()被设计在 poll 阶段结束后立即执行回调;
- setTimeout()被设计在指定下限时间到达后执行回调;
无 I/O 处理情况下:
1 2 3 4 5 6 7 |
|
执行结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
从结果,我们可以发现,这里打印输出出来的结果,并没有什么固定的先后顺序,偏向于随机,为什么会发生这样的情况呢?
答:首先进入的是timers
阶段,如果我们的机器性能一般,那么进入timers
阶段,1ms
已经过去了 ==(setTimeout(fn, 0)等价于setTimeout(fn, 1))==,那么setTimeout
的回调会首先执行。
如果没有到1ms
,那么在timers
阶段的时候,下限时间没到,setTimeout
回调不执行,事件循环来到了poll
阶段,这个时候队列为空,于是往下继续,先执行了setImmediate()的回调函数,之后在下一个事件循环再执行setTimemout
的回调函数。
问题总结:而我们在==执行启动代码==的时候,进入timers
的时间延迟其实是==随机的==,并不是确定的,所以会出现两个函数执行顺序随机的情况。
那我们再来看一段代码:
1 2 3 4 5 6 7 8 9 10 |
|
打印结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
这里,为啥和上面的随机timer
不一致呢,我们来分析下原因:
原因如下:fs.readFile
的回调是在poll
阶段执行的,当其回调执行完毕之后,poll
队列为空,而setTimeout
入了timers
的队列,此时有代码 setImmediate()
,于是事件循环先进入check
阶段执行回调,之后在下一个事件循环再在timers
阶段中执行回调。
当然,下面的小案例同理:
1 2 3 4 5 6 7 8 |
|
以上的代码在timers
阶段执行外部的setTimeout
回调后,内层的setTimeout
和setImmediate
入队,之后事件循环继续往后面的阶段走,走到poll阶段
的时候发现队列为空
,此时有代码有setImmedate()
,所以直接进入check阶段
执行响应回调(==注意这里没有去检测timers队列中是否有成员
到达下限事件,因为setImmediate()优先
==)。之后在第二个事件循环的timers
阶段中再去执行相应的回调。
综上所演示,我们可以总结如下:
- 如果两者都在主模块中调用,那么执行先后取决于进程性能,也就是你的电脑好撇,当然也就是随机。
- 如果两者都不在主模块调用(被一个异步操作包裹),那么**
setImmediate的回调永远先执行
**。
4. nextTick
与 Promise
概念:对于这两个,我们可以把它们理解成一个微任务。也就是说,它其实不属于事件循环的一部分。 那么他们是在什么时候执行呢? 不管在什么地方调用,他们都会在其所处的事件循环最后,事件循环进入下一个循环的阶段前执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
控制台打印如下:
1 2 3 4 5 6 7 8 9 |
|
最总结:timers
阶段执行外层setTimeout
的回调,遇到同步代码先执行,也就有timeout0
、sync
的输出。遇到process.nextTick
及Promise
后入微任务队列,依次nextTick1
、nextTick3
、nextTick2
、resolved
入队后出队输出。之后,在下一个事件循环的timers
阶段,执行setTimeout
回调输出timeout2
以及微任务Promise
里面的setTimeout
,输出timeout resolved
。(这里要说明的是 微任务nextTick
优先级要比Promise
要高)
5. 最后案例
代码片段1:
1 2 3 4 5 6 7 8 9 10 11 |
|
解析:
事件循环check
阶段执行回调函数输出setImmediate
,之后输出nextTick
。嵌套的setImmediate
在下一个事件循环的check
阶段执行回调输出嵌套的setImmediate
。
代码片段2:
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 |
|
打印结果为:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
大家呢,可以先看着代码,默默地在心底走一变代码,然后对比输出的结果,当然最后三位,我个人认为是有点问题的,毕竟在主模块运行,大家的答案,最后三位可能会有偏差;
更多node相关知识,请访问:nodejs 教程!
The above is the detailed content of An article explaining the event loop in Node.js in detail. For more information, please follow other related articles on the PHP Chinese website!

Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics

Node.js can be used as a backend framework as it offers features such as high performance, scalability, cross-platform support, rich ecosystem, and ease of development.

To connect to a MySQL database, you need to follow these steps: Install the mysql2 driver. Use mysql2.createConnection() to create a connection object that contains the host address, port, username, password, and database name. Use connection.query() to perform queries. Finally use connection.end() to end the connection.

The following global variables exist in Node.js: Global object: global Core module: process, console, require Runtime environment variables: __dirname, __filename, __line, __column Constants: undefined, null, NaN, Infinity, -Infinity

There are two npm-related files in the Node.js installation directory: npm and npm.cmd. The differences are as follows: different extensions: npm is an executable file, and npm.cmd is a command window shortcut. Windows users: npm.cmd can be used from the command prompt, npm can only be run from the command line. Compatibility: npm.cmd is specific to Windows systems, npm is available cross-platform. Usage recommendations: Windows users use npm.cmd, other operating systems use npm.

Detailed explanation and installation guide for PiNetwork nodes This article will introduce the PiNetwork ecosystem in detail - Pi nodes, a key role in the PiNetwork ecosystem, and provide complete steps for installation and configuration. After the launch of the PiNetwork blockchain test network, Pi nodes have become an important part of many pioneers actively participating in the testing, preparing for the upcoming main network release. If you don’t know PiNetwork yet, please refer to what is Picoin? What is the price for listing? Pi usage, mining and security analysis. What is PiNetwork? The PiNetwork project started in 2019 and owns its exclusive cryptocurrency Pi Coin. The project aims to create a one that everyone can participate

The main differences between Node.js and Java are design and features: Event-driven vs. thread-driven: Node.js is event-driven and Java is thread-driven. Single-threaded vs. multi-threaded: Node.js uses a single-threaded event loop, and Java uses a multi-threaded architecture. Runtime environment: Node.js runs on the V8 JavaScript engine, while Java runs on the JVM. Syntax: Node.js uses JavaScript syntax, while Java uses Java syntax. Purpose: Node.js is suitable for I/O-intensive tasks, while Java is suitable for large enterprise applications.

Yes, Node.js is a backend development language. It is used for back-end development, including handling server-side business logic, managing database connections, and providing APIs.

Node.js and Java each have their pros and cons in web development, and the choice depends on project requirements. Node.js excels in real-time applications, rapid development, and microservices architecture, while Java excels in enterprise-grade support, performance, and security.
