Home > Article > Web Front-end > In-depth understanding of Node event loop (EventLoop) mechanism
The main thread reads events from the "task queue". This process is cyclic, so the entire running mechanism is also called Event Loop. The following article will help you master the eventloop in Node.js. I hope it will be helpful to you!
Although js
can be executed in the browser and node
, their event loop mechanism is not the same. And there is a big difference.
Before talking about the Node
event loop mechanism, let us first discuss two issues
Learning the event loop can allow developers to understand how JavaScript
operates.
The event loop mechanism is used to manage when the callback function of the asynchronous API returns to the main thread for execution .
Node.js uses an asynchronous IO model. The synchronous API is executed in the main thread, the asynchronous API is executed in the thread maintained by the underlying C, and the callback function of the asynchronous API will also be executed in the main thread. [Recommended related tutorials: nodejs video tutorial, Programming teaching]
When a Javascript application is running, when can the callback functions of many asynchronous APIs return to the main thread? What about calls? This is what the event loop mechanism does, managing when the callback function of the asynchronous API returns to the main thread for execution.
The event loop in Node
is divided into six stages.
There is a queue at each stage in the event loop to store the callback functions to be executed. The event loop mechanism will execute them in a first-in, first-out manner until the queue is null.
These six stages all store asynchronous callback functions, so it is still necessary to execute the main thread synchronization code first, and then poll these six stages after the synchronization code is executed.
Next, let’s take a detailed look at what is stored in these six stages
Timers
: Callback function (setlnterval, setTimeout) used to store timers.
Pendingcallbacks
: Execute callback functions related to the operating system, such as the callback function that monitors port operations when starting a server-side application. Called here.
idle, prepare
: used internally by the system. (We programmers don’t need to worry about this)
Poll
: Stores the callback function queue for 1/O operations, such as file read and write operations callback function.
Special attention needs to be paid at this stage. If there are callback functions in the event queue, they will be executed until the queue is cleared. , otherwise the event loop will stay at this stage for a while to wait for a new callback function to enter.
But for this waiting is not certain, but depends on the following two conditions:
Check
: Stores the callback function of setlmmediate.
Closingcallbacks
: Execute callbacks related to closing events, such as callback functions for closing database connections, etc.
Like js
in the browser, the asynchronous code in node
is also divided into They are macrotasks and microtasks, but the execution order between them is different.
Let’s take a look at the macro tasks and micro tasks in Node
setlnterval
setimeout
setlmmediate
I/O
node, for microtasks and macros What is the order of execution of tasks?
In node
, the callback function of the microtask is placed in the microtask queue, and the callback function of the macrotask is placed in the macro task queue.
Microtasks have higher priority than macrotasks. When there is an executable callback function in the microtask event queue, the event loop will pause and enter the next stage of the event loop after executing the callback function of the current stage, and will immediately enter the microtask's event queue to start executing the callback function. When the callback function in the microtask queue is executed, the event loop will enter the next segment and start executing the callback function.
There is another point we need to pay special attention to when it comes to microtasks. That is, although nextTick
is also a micro-task, its priority is higher than other micro-tasks. When executing a micro-task, only after all callback functions in nextlick
are executed. Other microtasks will start to be executed.
In general, when the main thread synchronization code is executed, the microtasks will be cleared first (if the microtasks continue to generate microtasks, they will be cleared again), and then go to the next event loop stage. And the execution of microtasks is interspersed among the six stages of the event loop. That is, before each event loop enters the next stage, it will determine whether the microtask queue is empty. If it is empty, it will enter the next stage. Otherwise, the microtasks will be cleared first. queue.
Let’s use code practice to verify what we said above.
After the Node
application starts , will not enter the event loop immediately, but will execute the synchronous code first, starting from top to bottom. The synchronous API will be executed immediately, and the asynchronous API will be handed over to the thread maintained by C for execution. The callback function of the asynchronous API will be registered to the corresponding event queue. middle. When all synchronization codes are executed, the event loop will be entered.
console.log("start"); setTimeout(() => { console.log("setTimeout 1"); }); setTimeout(() => { console.log("setTimeout 2"); }); console.log("end");
Let’s look at the execution results
You can see that the synchronous code is executed first, and then the event loop is entered to execute the asynchronous code. In The timers
stage executes two setTimeout
callbacks.
We know that setTimeout
is executed in the timers
stage, setImmediate
is executed in the check
phase. And the event loop starts from the timers
phase. Therefore, setTimeout
will be executed first and then setImmediate
.
Is the above analysis correct?
Let’s look at the example
console.log("start"); setTimeout(() => { console.log("setTimeout"); }); setImmediate(() => { console.log("setImmediate"); }); const sleep = (delay) => { const startTime = +new Date(); while (+new Date() - startTime < delay) { continue; } }; sleep(2000); console.log("end");
Execute the above code, the output is as follows
Execute firstsetTimeout
then execute setImmediate
Next let’s transform the above code, remove the delayer, and see what will be output
setTimeout(() => { console.log("setTimeout"); }); setImmediate(() => { console.log("setImmediate"); });
We ran it seven times and you can see it Twice, setImmediate
was run first. What happened? Isn’t it first the timers
stage and then the check
stage? How could it change?
In fact, it depends on whether the asynchronous callback is fully ready when entering the event loop. For the initial example, because there is a delay of 2000 milliseconds, the setTimeout
callback must be ready when entering the event loop. So the order of execution will not change. But for this example, because the main thread has no synchronization code to execute, it enters the event loop at the beginning. However, when entering the event loop, the callback of setTimeout
is not necessarily fully prepared, so it will There is a callback function that first executes the setImmediate
in the check
stage, and then executes the setTimeout
callback in the timers
stage of the next event loop.
Under what circumstances will the setImmediate
callback function take precedence over the setTimeout
callback for the same delay time?
It's actually very simple, just put these two in the Pendingcallbacks, idle, prepare, poll
between the timers
stage and the check
stage Any one of the stages will do. Because after these stages are executed, they will definitely go to the check
stage and then the timers
stage.
We take the poll
stage as an example and write these two in the IO operation.
const fs = require("fs"); fs.readFile("./fstest.js", "utf8", (err, data) => { setTimeout(() => { console.log("setTimeout"); }); setImmediate(() => { console.log("setImmediate"); }); });
We also execute it seven times. You can see that setImmediate
is executed first every time.
So in general, with the same delay time, setTimeout
is not 100% executed before setImmediate
.
主线程同步代码执行完毕后,会先执行微任务再执行宏任务。
我们来看下面的例子
console.log("start"); setTimeout(() => { console.log("setTimeout"); }); setImmediate(() => { console.log("setImmediate"); }); Promise.resolve().then(() => { console.log("Promise.resolve"); }); console.log("end");
我们运行一下看结果,可以看到它是先执行了微任务然后再执行宏任务
在微任务中nextTick
的优先级是最高的。
我们来看下面的例子
console.log("start"); setTimeout(() => { console.log("setTimeout"); }); setImmediate(() => { console.log("setImmediate"); }); Promise.resolve().then(() => { console.log("Promise.resolve"); }); process.nextTick(() => { console.log("process.nextTick"); }); console.log("end");
我们运行上面的代码,可以看到就算nextTick
定义在resolve
后面,它也是先执行的。
怎么理解这个穿插呢?其实就是在事件循环的六个阶段每个阶段执行完后会清空微任务队列。
我们来看例子,我们建立了timers、check、poll
三个阶段,并且每个阶段都产生了微任务。
// timers阶段 setTimeout(() => { console.log("setTimeout"); Promise.resolve().then(() => { console.log("setTimeout Promise.resolve"); }); }); // check阶段 setImmediate(() => { console.log("setImmediate"); Promise.resolve().then(() => { console.log("setImmediate Promise.resolve"); }); }); // 微任务 Promise.resolve().then(() => { console.log("Promise.resolve"); }); // 微任务 process.nextTick(() => { console.log("process.nextTick"); Promise.resolve().then(() => { console.log("nextTick Promise.resolve"); }); });
我们来执行上面的代码
可以看到,先执行微任务,再执行宏任务。先process.nextTick -> Promise.resolve
。并且如果微任务继续产生微任务则会再次清空,所以就又输出了nextTick Promise.resolve
。
接下来到timer
阶段,输出setTimeout
,并且产生了一个微任务,再进入到下个阶段前需要清空微任务队列,所以继续输出setTimeout Promise.resolve
。
接下来到check
阶段,输出setImmediate
,并且产生了一个微任务,再进入到下个阶段前需要清空微任务队列,所以继续输出setImmediate Promise.resolve
。
这也就印证了微任务会穿插在各个阶段之间运行。
所以对于Node
中的事件循环你只需要背好一以下几点就可以了
当主线程同步代码执行完毕后才会进入事件循环
事件循环总共分六个阶段,并且每个阶段都包括哪些回调需要记清楚。
事件循环中会先执行微任务再执行宏任务。
微任务会穿插在这六个阶段之间执行,每进入到下个阶段前会清空当前的微任务队列。
微任务中process.nextTick
的优先级最高,会优先执行。
更多node相关知识,请访问:nodejs 教程!
The above is the detailed content of In-depth understanding of Node event loop (EventLoop) mechanism. For more information, please follow other related articles on the PHP Chinese website!