Home >Web Front-end >JS Tutorial >An in-depth analysis of the microtask queue in the Node event loop
In previous articles, we learned that the event loop is a key part of Node.js, used to coordinate the execution of synchronous and asynchronous code.
It consists of six different queues. A nextTick queue and a Promise queue (called a microtask queue), a timer queue, an I/O queue, a check queue, and finally the shutdown queue. [Related tutorial recommendations: nodejs video tutorial, Programming teaching]
In each loop, the callback function will be dequeued (dequeued) at the appropriate time and called executed on the stack. Starting from this article, we'll run some experiments to make sure our understanding of the event loop is correct.
Our first set of experiments will focus on the nextTick queue and the Promise queue. But before we dive into the experiment, we need to understand how to queue callback functions.
Note: All experiments were conducted using the CommonJS module format.
To add the callback function to the nextTick queue, we can use the built-in process.nextTick()
method. The syntax is very simple: process.nextTick(callbackFn)
. When the method is executed on the call stack, the callback function will be added to the nextTick queue.
To add the callback function to the Promise queue, we need to use Promise.resolve().then(callbackFn)
. When the Promise resolves, the callback function passed to then()
will be enqueued into the Promise queue.
Now that we know how to add callback functions to two queues, let's start our first experiment.
// index.js console.log("console.log 1"); process.nextTick(() => console.log("this is process.nextTick 1")); console.log("console.log 2");
This code records three different statements. The second statement uses process.nextTick()
to enqueue the callback function into the nextTick queue.
The first console.log()
statement is pushed into the call stack for execution . It logs the appropriate message in the console and then pops the stack.
Next, execute process.nextTick()
on the call stack, enqueue the callback function into the nextTick queue, and pop it up. There is still user-written code that needs to be executed at this point, so the callback function must wait its turn.
Execution continues, the last console.log()
statement is pushed onto the stack, the message is logged in the console, and the function is popped off the call stack. Now, there is no more user-written synchronous code to execute, so control goes into the event loop.
The callback function in the nextTick queue is pushed into the call stack, console.log()
is pushed into the call stack and executed, and the corresponding message is recorded on the console.
console.log 1 console.log 2 this is process.nextTick 1
All user-written synchronous JavaScript code executes before asynchronous code.
Let's move on to the second experiment.
// index.js Promise.resolve().then(() => console.log("this is Promise.resolve 1")); process.nextTick(() => console.log("this is process.nextTick 1"));
We have a Promise.resolve().then() call and a process.nextTick() call.
#When the call stack executes line 1, it queues the callback function into the Promise queue; when the call stack When line 2 is executed, it queues the callback function into the nextTick queue.
No code needs to be executed after line 2.
Control goes into the event loop, where the nextTick queue takes precedence over the Promise queue (this is how the Node.js runtime works).
The event loop executes the nextTick queue callback function, and then executes the Promise queue callback function.
The console displays "this is process.nextTick 1", then "this is Promise.resolve 1".
this is process.nextTick 1 this is Promise.resolve 1
All callback functions in the nextTick queue are executed prior to callback functions in the Promise queue.
Let me walk you through a more detailed version of the second experiment above.
// index.js process.nextTick(() => console.log("this is process.nextTick 1")); process.nextTick(() => { console.log("this is process.nextTick 2"); process.nextTick(() => console.log("this is the inner next tick inside next tick") ); }); process.nextTick(() => console.log("this is process.nextTick 3")); Promise.resolve().then(() => console.log("this is Promise.resolve 1")); Promise.resolve().then(() => { console.log("this is Promise.resolve 2"); process.nextTick(() => console.log("this is the inner next tick inside Promise then block") ); }); Promise.resolve().then(() => console.log("this is Promise.resolve 3"));
This code contains three process.nextTick()
call and three Promise.resolve()
call statements. Each callback function logs the appropriate message.
但是,第二个 process.nextTick()
和第二个 Promise.resolve()
都有一个额外的 process.nextTick()
语句,每个都带有一个回调函数。
为了加快对此可视化的解释,我将省略调用栈讲解。当调用栈执行所有 6 个语句时,nextTick 队列中有 3 个回调函数,Promise 队列中也有 3 个回调函数。没有其他要执行的内容后,控制权进入事件循环。
我们知道,nextTick 队列中具有高优先级。首先执行第 1 个回调函数,并将相应的消息记录到控制台中。
接下来,执行第 2 个回调函数,记录了第 2 条日志语句。但是,这个回调函数包含另一个对 process.nextTick()
的调用,该方法内的回调函数(译注:即() => console.log("this is the inner next tick inside next tick")
)入队到nextTick 队列末尾。
然后 Node.js 执行第 3 个回调函数并将相应消息记录到控制台上。最初只有 3 个回调,但是因为第 2 次时向nextTick 队列又添加了一个,所以变成 4 个了。
事件循环将第 4 个回调函数推入调用栈,执行 console.log()
语句。
接着处理完 nextTick 队列之后,控制流程进入到 Promise 队列。Promise 队列与 nextTick 队列处理方式类似。
首先记录“Promise.resolve 1”,然后是“Promise.resolve 2”,这时因为调用 process.nextTick() 的原因,一个函数(译注:即 () => console.log("this is the inner next tick inside Promise then block")
) 被推入 nextTick 队列了。尽管如此,控制流程仍停留在 Promise 队列,因此还会继续执行队列中的其他函数。然后我们得到“Promise.resolve 3”,此时 Promise 队列为空。
Node.js 将再次检查微任务队列中是否有新的回调。由于 nextTick 队列中有一个回调,因此会执行这个回调,导致我们的最后一条日志输出。
this is process.nextTick 1 this is process.nextTick 2 this is process.nextTick 3 this is the inner next tick inside next tick this is Promise.resolve 1 this is Promise.resolve 2 this is Promise.resolve 3 this is the inner next tick inside Promise then block
这可能是一个稍微高级的实验,但推论仍然相同。
nextTick 队列中的所有回调函数优先于 Promise 队列中的回调函数执行。
使用 process.nextTick()
时要小心。过度使用此方法可能会导致事件循环饥饿,从而阻止队列中的其余部分运行。当存在大量的 nextTick() 调用时,I/O 队列是无法执行自己的回调函数的。官方文档建议使用 process.nextTick()
的两个主要场景:处理错误或在调用栈为空事件循环继续之前执行回调用。所以在使用 process.nextTick()
时,要明智一些。
实验表明,用户编写的所有同步 JavaScript 代码优先于异步代码执行,并且 nextTick 队列中的所有回调函数优先于 Promise 队列中的回调函数执行。
原文链接:Visualizing nextTick and Promise Queues in Node.js Event Loop,2023年3月30日,by Vishwas Gopinath
更多node相关知识,请访问:nodejs 教程!
The above is the detailed content of An in-depth analysis of the microtask queue in the Node event loop. For more information, please follow other related articles on the PHP Chinese website!