event loop
Today I want to share the impact of nodejs type: "module" on the event loop.
First, let’s review the basic knowledge of the event loop.
Generally synchronous--> asynchronous, macro-task--> micro-task (macro-tasks are divided into asynchronous and synchronous, < /span> Loop in sequence)Synchronous macro--> Micro--> Asynchronous macro
The details areSynchronization task (script) -----> Clear microtask queue-----> Macro task ...loop
Note thatclear the microtask. If you add the microtask again during the microtask, the microtask will continue to be executed
If a micro task is found during the macro task, even if there are still tasks waiting in the current macro task queue, they will be processed first Complete the micro tasks first before moving on to the next macro task
What are the macro tasks and micro tasks?
Macro tasks:
- script (can be understood as outer layer synchronization code)
- setTimeout/setInterval
- UI rendering/UI incident
- postMessage,MessageChannel
- I/O operation,setImmediate (Node.js) (What is setImmediate?setImmediate? a>setImmediate has a higher priority than setTimeout ) (because the timer cannot actually be 0ms, it will be later than setImmediate)
Microtasks:
- Promise.then
- queueMicrotask(() => {}) Create a microtask
- Object.observe (Deprecated; replaced by Proxy object)
- MutaionObserver
NodeJS-likenextTick column:
- process.nextTick (Node.js) (has a separate queue, is executed earlier than the microtask)
Official original words: In each round of the Node.js event loop, the process.nextTick() queue is always processed before the microtask queue
Something goes wrong - In NodeJS, run this code
In fact, today’s focus is not on the above content. With the above knowledge, we try to run the following code:
console.log('1');
setTimeout(function () {//time1
console.log('2');
new Promise(function (resolve) {
console.log('4');
resolve();
}).then(function () {//then1
console.log('5')
})
process.nextTick(function () {//next1
console.log('3');
})
})
process.nextTick(function () {//next2
console.log('6');
})
new Promise(function (resolve) {
console.log('7');
resolve();
}).then(function () {//then2
console.log('8')
})
setTimeout(function () {//time2
console.log('9');
process.nextTick(function () {//next3
console.log('10');
})
new Promise(function (resolve) {
console.log('11');
resolve();
}).then(function () {//then3
console.log('12')
})
})
According to our conventional analysis, nextTick is executed before the microtask, and the result should be 1 7 6 8< a i=3> 2 4 3 5 9 11 10 12
When the type of package.json is not set, or is set to "commonjs", the output result is indeed in line with our ideas.
But when setting type:"module", the output result changes and we get1 7 8 6 2 4 3 5 9 11 10 12
Problem analysis and solution
You can try a few more pieces of code where Promise.then and process.nextTick appear at the same time, and you will find that they are inconsistent with expectations.
Can we simply think that "microtasks in module mode have higher priority than then"?
No! Return to the above code and observe again, and you will find that "Promise.then and process.nextTick appear at the same time" multiple times, but in the end a>. In other words, only the outermost microtasks and nextTick have the problem of incorrect order. Only the outermost layer has a sequence change
After reviewing multiple materials with everyone, the final reason is actually: In ESM mode, the code actually runs under async/await
In other words, the processed ESM code actually looks like this
So, when ESM is running, the code is actually in the microtask phase, and the microtask queue must be cleared before it is nextTick's turn. CommonJS runs synchronously, so you get the expected results
This can also be a good explanation for why only the outermost nexttick has order problems. In fact, the rules are the same as the official documentation:In the Node.js event loop In each round, the process.nextTick() queue is always processed before the microtask queue. The outermost code is in the microtask, and of course there is no nextTick turn. NextTick in setTimeout will be executed before Promise.then.
Now come back to this code:
// CommonJS模式 - 普通分析即可
// 在esm模式中, 由于esm会被包裹在await后执行,所以相当于第一轮代码执行是微任务,这时候的NextTick需要等待微任务清空完成后才能执行。所以先输出 8 再 6。
// 后续在time1中又遇到了nextTick和微任务同时出现的情况,这时候就是普通宏任务环境了,所以 nextTick优先级高于微任务, 先输出 3 再 5
const d = () => {
console.log('1');
setTimeout(function () {//time1
console.log('2');
new Promise(function (resolve) {
console.log('4');
resolve();
}).then(function () {//then1
console.log('5')
})
process.nextTick(function () {//next1
console.log('3');
})
})
process.nextTick(function () {//next2
console.log('6');
})
new Promise(function (resolve) {
console.log('7');
resolve();
}).then(function () {//then2
console.log('8')
})
setTimeout(function () {//time2
console.log('9');
process.nextTick(function () {//next3
console.log('10');
})
new Promise(function (resolve) {
console.log('11');
resolve();
}).then(function () {//then3
console.log('12')
})
})
}
d()
Summarize
When you encounter the event loop problem during the interview, if you encounter nextTick,explain the difference between CommonJS and ESM clearly, I believe it will be helpful to you Brings extra points
references:
- Why the order is inconsistent (see comments):NodeJS process.nextTick and queueMicrotask in commonJs and ESM, what is the execution order? _Big data knowledge base (saoniuhuo.com)
- Why ESM is asynchronous: How Node.js handles ES6 modules - Ruan Yifeng's network log (ruanyifeng.com)