Home > Article > Web Front-end > JavaScript execution mechanism and event loop in nodejs environment
This article brings you an introduction to the usage of JavaScript high-order functions. It has certain reference value. Friends in need can refer to it. I hope it will be helpful to you.
1. Description
nodejs is executed in a single thread, and it is based on an event-driven non-blocking IO programming model. This allows us to continue executing the code without waiting for the asynchronous operation result to return. When an asynchronous event is triggered, the main thread will be notified, and the main thread will execute the callback of the corresponding event.
This article explains the execution process of JavaScript code in node. The following is the test code. If you know the output result, then you don’t need to read this article. If you don’t know the output result, then this video The article can help you understand:
console.log(1) setTimeout(function () { new Promise(function (resolve) { console.log(2) resolve() }) .then(() => { console.log(3) }) }) setTimeout(function () { console.log(4) })
Complex:
setTimeout(() => { console.log('1') new Promise((resolve) => { console.log('2'); resolve(); }) .then(() => { console.log('3') }) new Promise((resolve)=> { console.log('4'); resolve()}) .then(() => { console.log('5') }) setTimeout(() => { console.log('6') setTimeout(() => { console.log('7') new Promise((resolve) => { console.log('8'); resolve() }) .then( () => { console.log('9') }) new Promise((resolve) => { console.log('10'); resolve() }) .then(() => { console.log('11') }) }) setTimeout(() => { console.log('12') }, 0) }) setTimeout(() => { console.log('13') }, 0) }) setTimeout(() => { console.log('14') }, 0) new Promise((resolve) => { console.log('15'); resolve() }) .then( ()=> { console.log('16') }) new Promise((resolve) => { console.log('17'); resolve() }) .then(() => { console.log('18') })
2. The startup process of nodejs
The node.js startup process can be divided into the following Steps:
3. Detailed explanation of nodejs event loop
Nodejs subdivides the message loop into 6 stages (official Called Phase), each stage will have a queue-like structure that stores the callback functions that need to be processed in that stage.
Nodejs In order to prevent too many tasks in a certain stage from causing starvation in subsequent stages , so in each iteration of the message loop, there is a maximum number of callbacks executed in each stage. If the number is exceeded, the current stage will be forcibly ended and the next stage will be entered. This rule applies to every callback in the message loop. One stage.
This is the first stage of the message loop, using a for
loop to process all setTimeout
and setInterval
callbacks.
These callbacks are saved in a minimum heap (min heap). In this way, the engine only needs to judge the header element each time, and if it meets the conditions, it will be taken out and executed until it encounters a The Timer Phase ends only when the conditions are not met or the queue is empty.
The method to determine whether a certain callback meets the conditions in the Timer phase is also very simple. The message loop will save the current system time every time it enters the Timer. , and then just check whether the startup time set by the callback function in the above minimum heap exceeds the time saved when entering the Timer. If it exceeds, take it out and execute it.
Execute almost all callbacks except close callbacks
, setTimeout()
, setInterval()
, setImmediate()
callbacks, for exampleTCP connection error
, fs.read
, socket
and other callback functions for IO operations, as well as callbacks for various errors.
Some calls within the system.
This is the most important stage in the entire message cycle. Its function is to wait for asynchronous requests and data, because it supports the entire message cycle mechanism.
The poll phase has two main functions: one is to execute the callback of timers whose lower limit time has been reached, and the other is to process events in the poll queue.
Note: Many APIs of Node are based on event subscription, such as fs.readFile. These callbacks should be completed in the poll
stage.
When the event loop enters the poll phase:
#poll
When the queue is not empty, the event loop must first traverse the queue and execute the callback synchronously until the queue The number of cleared or executed callbacks reaches the system limit. poll
When the queue is empty, there are two situations.
setImmediate()
, then the event loop directly ends the poll
stage and enters the check
stage. Execute the callback in the check
queue. If the code is not setsetImmediate()
Set callback:
In the Poll phase, when none of the event callbacks registered by the js layer code returns, the event loop will temporarily block in the poll phase and unblock it. Conditions:
- When the poll phase is executed, a timeout timeout will be passed in, which is the maximum blocking time of the poll phase.
- When the timeout time has not expired, if an event returns, the callback function registered for the event will be executed. When the timeout timeout expires, the poll phase will exit and the next phase will be executed.
What is the appropriate setting for this timeout? The answer is the difference between the start time of the callback to be executed recently in the Timer Phase and the present. Assume that the difference is detal. Because there is nothing waiting to be executed after the Poll Phase. Callback. So the maximum waiting time here is delta. If an event wakes up the message loop during the period, then continue the work of the next Phase; if nothing happens during the period, then after timeout, the message loop still has to enter the subsequent Phase, so The Timer Phase of the next iteration can also be executed.
Nodejs drives the entire message loop through the Poll Phase, waiting for IO events and the arrival of kernel asynchronous events.
This stage only handles the callback function of setImmediate.
So why is there a special stage to handle setImmediate? Simply put, it is because the Poll stage may set some callbacks and hope to run after the Poll stage . So the Check stage is added after the Poll stage.
Specializes in handling some close type callbacks. For example, socket.on('close', .. .)
. Used for resource cleaning.
1. Node initialization
Initialize node environment
Execute the entered code
Executeprocess.nextTick
Callback
Execute microtasks
2. Enter the event loop
2.1. Entering the Timer
stage
Timer
queue has an expired Timer
callback. If so, Execute all expired Timer
callbacks in ascending order by TimerId
process.nextTick
tasks, if so, execute them all2.2, enter Pending I/O Callback
Phase
Pending I/O Callback
, and if so, executes the callback. If you do not exit this stage
process.nextTick
tasks, if so, execute them all2.3. Enter idle, prepare
stage
This stage has little to do with JavaScript , skip
2.4 and enter the Poll
stage
First check whether there is an unfinished callback. If it exists, there are two situations:
The first case: there is an executable callback
Execute all available callbacks (including expired timers and some IO events, etc.)
Check whether there is process.nextTick
Tasks, if there are any, execute them all
Check whether there are microtasks (promise), if there are, execute them all
Exit this stage
Second type Situation: There is no executable callback
Check if there is an immediate
callback, if so, exit the Poll phase. If not, block in this stage, waiting for new event notification
If there is no unfinished callback, exit the Poll stage
2.5, entercheck
Phase
If there is an immediate callback, execute all immediate callbacks
Check whether there are process.nextTick
tasks, if so, execute them all
Check whether there are microtasks (promise), if so, execute them all
Exit this stage
2.6, enter the closing
stage
if If there is an immediate callback, execute all immediate callbacks
Check whether there are process.nextTick
tasks, if so, execute them all
Check whether there are microtasks (promise), if Yes, execute all
Exit this stage
3. Check whether there are active handles (timer, IO and other event handles)
If there are , continue the next round of event loop
If not, end the event loop and exit the program
Note:
Each sub-stage of the event loop will execute the following process in sequence before exiting:
Check whether there is a process.nextTick callback, and if so, execute them all.
Check whether there are microtasks (promise), and if so, execute them all.
事件循环队列先保证所有的process.nextTick
回调,然后将所有的Promise
回调追加在后面,最终在每个阶段结束的时候一次性拿出来执行。
此外,process.nextTick
和Promise
回调的数量是受限制的,也就是说,如果一直往这个队列中加入回调,那么整个事件循环就会被卡住
。
这两个方法的回调到底谁快?
如下面的例子:
setImmediate(() => console.log(2)) setTimeout(() => console.log(1))
使用nodejs多次执行后,发现输出结果有时是1 2
,有时是2 1
。
对于多次执行输出结果不同,需要了解事件循环的基础问题。
首先,Nodejs启动,初始化环境后加载我们的JS代码(index.js).发生了两件事(此时尚未进入消息循环环节):
setImmediate 向 Check 阶段 中添加了回调 console.log(2);setTimeout 向 Timer 阶段 中添加了回调 console.log(1)
这时候, 要初始化阶段完毕, 要进入 Nodejs 消息循环了。
为什么会有两种输出呢? 接下来一步很关键:
当执行到 Timer 阶段 时, 会发生两种可能. 因为每一轮迭代刚刚进入 Timer 阶段 时会取系统时间保存起来, 以 ms(毫秒) 为最小单位.
如果 Timer 阶段 中回调预设的时间 > 消息循环所保存的时间, 则执行 Timer 阶段 中的该回调. 这种情况下先输出 1, 直到 Check 阶段 执行后,输出2.总的来说, 结果是 1 2.
如果运行比较快, Timer 阶段 中回调预设的时间可能刚好等于消息循环所保存的时间, 这种情况下, Timer 阶段 中的回调得不到执行, 则继续下一个 阶段. 直到 Check 阶段, 输出 2. 然后等下一轮迭代的 Timer 阶段, 这时的时间一定是满足 Timer 阶段 中回调预设的时间 > 消息循环所保存的时间 , 所以 console.log(1) 得到执行, 输出 1. 总的来说, 结果就是 2 1.
所以, 输出不稳定的原因就取决于进入 Timer 阶段 的时间是否和执行 setTimeout 的时间在 1ms 内. 如果把代码改成如下, 则一定会得到稳定的输出:
require('fs').readFile('my-file-path.txt', () => { setImmediate(() => console.log(2)) setTimeout(() => console.log(1)) });
这是因为消息循环在 Pneding I/O Phase
才向 Timer 和 Check 队列插入回调. 这时按照消息循环的执行顺序, Check 一定在 Timer 之前执行。
从性能角度讲, setTimeout 的处理是在 Timer Phase, 其中 min heap 保存了 timer 的回调, 因此每执行一个回调的同时都会涉及到堆调整. 而 setImmediate 仅仅是清空一个队列. 效率自然会高很多.
再从执行时机上讲. setTimeout(..., 0) 和 setImmediate 完全属于两个阶段.
下面以一段代码来说明nodejs运行JavaScript的机制。
如下面一段代码:
setTimeout(() => { // settimeout1 console.log('1') new Promise((resolve) => { console.log('2'); resolve(); }) // Promise3 .then(() => { console.log('3') }) new Promise((resolve)=> { console.log('4'); resolve()}) // Promise4 .then(() => { console.log('5') }) setTimeout(() => { // settimeout3 console.log('6') setTimeout(() => { // settimeout5 console.log('7') new Promise((resolve) => { console.log('8'); resolve() }) // Promise5 .then( () => { console.log('9') }) new Promise((resolve) => { console.log('10'); resolve() }) // Promise6 .then(() => { console.log('11') }) }) setTimeout(() => { console.log('12') }, 0) // settimeout6 }) setTimeout(() => { console.log('13') }, 0) // settimeout4 }) setTimeout(() => { console.log('14') }, 0) // settimeout2 new Promise((resolve) => { console.log('15'); resolve() }) // Promise1 .then( ()=> { console.log('16') }) new Promise((resolve) => { console.log('17'); resolve() }) // Promise2 .then(() => { console.log('18') })
上面代码执行过程:
node初始化
执行JavaScript代码
遇到setTimeout
, 把回调函数放到Timer
队列中,记为settimeout1
遇到setTimeout
, 把回调函数放到Timer
队列中,记为settimeout2
遇到Promise
,执行,输出15,把回调函数放到微任务
队列,记为Promise1
遇到Promise
,执行,输出17,把回调函数放到微任务
队列,记为Promise2
代码执行结束,此阶段输出结果:15 17
没有process.nextTick
回调,略过
执行微任务
检查微任务队列是否有可执行回调,此时队列有2个回调:Promise1、Promise2
执行Promise1回调,输出16
执行Promise2回调,输出18
此阶段输出结果:16 18
进入第一次事件循环
进入Timer阶段
检查Timer队列是否有可执行的回调,此时队列有2个回调:settimeout1、settimeout2
执行settimeout1回调:
输出1、2、4
添加了2个微任务,记为Promise3、Promise4
添加了2个Timer任务,记为settimeout3、settimeout4
执行settimeout2回调,输出14
Timer队列任务执行完毕
没有process.nextTick
回调,略过
检查微任务队列是否有可执行回调,此时队列有2个回调:Promise3、Promise4
按顺序执行2个微任务,输出3、5
此阶段输出结果:1 2 4 14 3 5
Pending I/O Callback阶段没有任务,略过
进入 Poll 阶段
检查是否存在尚未完成的回调,此时有2个回调:settimeout3、settimeout4
Execute settimeout3 callback
Output 6
Added 2 Timer tasks, recorded as settimeout5, settimeout6
Execute settimeout4 callback, output 13
There is no process.nextTick
callback, skip
There is no microtask, skip
The output result of this stage: 6 13
There are no tasks in the check and closing phases, skip them
Check whether there are still active handles (timers, IO and other event handles)
, yes, continue to the next round of event loop
Enter the second event loop
Enter the Timer phase
Check whether the Timer queue has an executable callback, this The queue has 2 callbacks: settimeout5, settimeout6
Execute settimeout5 callback:
Output 7, 8, 10
Added 2 microtasks , recorded as Promise5, Promise6
executes the settimeout6 callback, outputs 12
There is no process.nextTick
callback, skip
Check whether the microtask queue has executable callbacks. At this time, the queue has 2 callbacks: Promise5, Promise6
Execute 2 microtasks in order, output 9, 11
Output results of this stage: 7 8 10 12 9 11
There are no tasks in the Pending I/O Callback, Poll, check, and closing phases, so skip them
Check if there are still active handles (timer, IO and other event handles)
, if there are no more, end the event loop and exit the program
End of program execution , output result: 15 17 16 18 1 2 4 14 3 5 6 13 7 8 10 12 9 11
[Related recommendations: JavaScript video tutorial】
The above is the detailed content of JavaScript execution mechanism and event loop in nodejs environment. For more information, please follow other related articles on the PHP Chinese website!