Introduction
Although js
it can be executed in the browser and in the browser node
, their event loop mechanisms are not the same. And there is a big difference.
If you are not familiar with the browser event loop mechanism, you can read what the author wrote earlier in 2022. You don’t know the js execution context and event loop mechanism . Today we only talk about Node
the event loop mechanism in China.
EventLoop Mechanism Overview
Before talking about Node
the event loop mechanism, let's discuss two issues
Why learn the event loop mechanism?
Learning the event loop allows developers to understand JavaScript
what the operating mechanism is like.
What does the event loop mechanism do?
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 is also executed in the main thread.
When the Javascript application is running, when can the callback functions of many asynchronous APIs be called back to the main thread? 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 six stages of EventLoop
The event loop in Node
is divided into six phases.
Each stage in the event loop has a queue to store the callback functions to be executed, and the event loop mechanism will execute them in a first-in-first-out manner until the queue is empty.
These six stages all store asynchronous callback functions, so the main thread synchronization code is executed first, and these six stages are polled after the synchronization code is executed.
Next, let's take a closer look at what is stored in these six stages
Timers
Timers
: The callback function used to store the timer (setlnterval, setTimeout).
Pendingcallbacks
Pendingcallbacks
: Execute the callback function related to the operating system, such as the callback function for listening to the port operation when starting the server-side application is called here.
idle,prepare
idle,prepare
: Used internally by the system. (We programmers don't care about this)
Poll
Poll
: Store the callback function queue for I/O operations, such as the callback function for file read and write operations.
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 emptied
, otherwise the event loop will stay in this stage for a while waiting for new callback functions to enter.
But this wait is not certain, but depends on the following two conditions:
- If there is a call function to be executed in the setlmmediate queue (check phase). This situation will not wait.
- There is a callback function to be executed in the timers queue, and it will not wait in this case. The event loop will move to the check phase, then to the Closing callbacks phase, and finally from the timers phase to the next cycle.
Check
Check
: Store the callback function of setlmediate.
Closingcallbacks
Closingcallbacks
: Execute the callback related to the close event, such as the callback function for closing the database connection, etc.
Macrotasks and Microtasks
Just like in the browser js
, node
the asynchronous code in is also divided into macrotasks and microtasks, but the order of execution between them is different.
Let's take a look at Node
what macro tasks and micro tasks are in
macro task
- setlnterval
- setimeout
- setlmmediate
- I/O
micro task
- Promise.then
- Promise.catch
- Promise.finally
- process.nextTick
In node
, what is the execution order of microtasks and macrotasks?
Execution order of microtasks and macrotasks
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 macrotask 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 event queue of the microtask to start executing the callback function. After the execution of the callback function in the microtask queue is completed, the event loop will enter the next segment and start executing the callback function.
There is one more point we need to pay special attention to for microtasks. That is, although nextTick
it belongs to microtasks, its priority is higher than other microtasks. When executing microtasks, other nextlick
microtasks will not be executed until all callback functions in it are executed.
In general, when the main thread synchronization code is executed, the microtask will be cleared first (if the microtask continues to generate microtasks, it will be cleared again), and then go to the next event cycle 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 judge whether the microtask queue is empty, and it will enter the next stage if it is empty, otherwise the microtask will be cleared first queue.
Let's use code practice to verify what we said earlier.
code example
Execute synchronously before asynchronously
After Node
the application starts, it does not immediately enter the event loop, but executes the synchronous code first, starting from top to bottom. The synchronous API is executed immediately, and the asynchronous API is handed over to the thread maintained by C++ for execution. The callback function of the asynchronous API is registered. to the corresponding event queue. The event loop will not be entered until all synchronous code execution is complete.
console.log("start");
setTimeout(() => {
console.log("setTimeout 1");
});
setTimeout(() => {
console.log("setTimeout 2");
});
console.log("end");
Let's see the execution result
It can be seen that the synchronous code is executed first, and then the event loop is executed to execute the asynchronous code, and timers
two setTimeout
callbacks are executed in the stage.
Will setTimeout be executed before setImmediate?
We know setTimeout
that it is carried out in timers
stages, setImmediate
it is carried out in check
stages. And the event loop is timers
started from phase. So it will be executed first setTimeout
and then executed setImmediate
.
Is the above analysis correct?
Let's look at an 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 first setTimeout
and then executesetImmediate
Next, let's modify the above code, remove the delayer, and see what will be output
setTimeout(() => {
console.log("setTimeout");
});
setImmediate(() => {
console.log("setImmediate");
});
We run it seven times and we can see that two of them are run firstsetImmediate
What's going on? Isn't it the first timers
stage and then check
the stage? How can it change?
In fact, it depends on whether the asynchronous callback is fully ready when entering the event loop. For the first example, because there is a delay of 2000 milliseconds, when entering the event loop, setTimeout
the callback must be ready. So the order of execution will not change. But for this example, because the main thread has no synchronous code to execute, it enters the event loop at the beginning, but when entering the event loop, the setTimeout
callback is not necessarily fully prepared, so there will be a first check
stage to execute setImmediate
the callback function , and then to the next event loop timers
stage to execute setTimeout
the callback.
Under what circumstances setImmediate
will the callback function be prioritized over the callback for the same delay time setTimeout
?
In fact, it is very simple, as long as the two are placed in any stage of timers
the stage and the stage check
between the stages . Pendingcallbacks、idle,prepare、poll
Because these stages are completed and executed, they will definitely come first check
and then come timers
to the stage.
Let's take poll
the 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");
});
});
Let's also execute it seven times, as you can see, each time it is setImmediate
executed first.
So in general, the same delay time, setTimeout
not 100% ahead of setImmediate
execution.
Microtasks before macrotasks
After the main thread synchronization code is executed, the microtask will be executed first and then the macrotask will be executed.
Let's look at the following example
console.log("start");
setTimeout(() => {
console.log("setTimeout");
});
setImmediate(() => {
console.log("setImmediate");
});
Promise.resolve().then(() => {
console.log("Promise.resolve");
});
console.log("end");
Let's run it to see the result, we can see that it executes the microtask first and then executes the macrotask
nextTick is superior to other microtasks
nextTick
Priority is highest among microtasks .
Let's look at the following example
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");
When we run the above code, we can see that even if it nextTick
is defined resolve
later, it is executed first.
Microtasks are interspersed between stages
How to understand this interlude? In fact, the microtask queue will be cleared after each of the six stages of the event loop is executed.
Let's look at an example, we have established timers、check、poll
three stages, and each stage generates microtasks.
// 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");
});
});
Let's execute the above code
As you can see, the microtasks are executed first, and then the macrotasks are executed. first process.nextTick -> Promise.resolve
. And if the microtask continues to generate microtasks, it will be cleared again, so it will be output again nextTick Promise.resolve
.
The next timer
stage is output setTimeout
, and a microtask is generated, and the microtask queue needs to be cleared before entering the next stage, so continue to output setTimeout Promise.resolve
.
The next check
stage is output setImmediate
, and a microtask is generated, and the microtask queue needs to be cleared before entering the next stage, so continue to output setImmediate Promise.resolve
.
This also confirms that microtasks will be interspersed between various stages.
Summarize
So Node
you only need to memorize the following points for the event loop in
- When the main thread synchronization code is executed, it will enter the event loop
- The event cycle is divided into six phases, and which callbacks are included in each phase need to be remembered clearly.
- In the event loop, microtasks are executed first and then macrotasks are executed.
- Microtasks will be executed between these six stages, and the current microtask queue will be cleared before entering the next stage.
- The microtask
process.nextTick
has the highest priority and will be executed first.
series of articles
Introduction to Node.js What is Node.js
The path module for getting started with Node.js
The fs module for getting started with Node.js
The url module and querystring module for getting started with Node.js
http module and dns module for getting started with Node.js
Node.js entry process module, child_process module, cluster module
I heard you don't know how to use Express yet?
I heard you don't know how to use Koa yet?
postscript
This article is the author's personal study notes, if there is any fallacy, please let me know, thank you very much! If this article is helpful to you, please give it a follow and a like~, your support is the motivation for the author to keep updating.