Concept
eventloop 概览
┌───────────────────────────┐
┌─>│ timers │ (this phase executes callbacks scheduled by setTimeout() and setInterval())
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ pending callbacks │ (executes I/O callbacks deferred to the next loop iteration.)
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ idle, prepare │ (only used internally)
│ └─────────────┬─────────────┘ ┌───────────────┐
│ ┌─────────────┴─────────────┐ │ incoming: │
│ │ poll │<─────┤ connections, │ (retrieve new I/O events; execute I/O related callbacks)
│ └─────────────┬─────────────┘ │ data, etc. │
│ ┌─────────────┴─────────────┐ └───────────────┘
│ │ check │ (setImmediate() callbacks are invoked here)
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
└──┤ close callbacks │ (some close callbacks, e.g. socket.on('close', ...))
└───────────────────────────┘
详情请参考 https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/
Macrotask and Microtask
eventloop的一次循环,会从 macrotask queue取出一个任务, 任务完成后,所有microtask queue的微任务都会被处理完,然后继续从macrotask queue取任务……微任务在被处理的过程中可以创建其他的微任务,并且这些微任务也会被加入 microtask queue并被一一执行。
如上所述,如果一个微任务不断创建新的微任务,则可能会对其他宏任务造成“饥饿”现象,因为微任务队列很难被清空。
Examples:
macrotasks: setTimeout, setInterval, setImmediate, requestAnimationFrame, I/O, UI rendering
microtasks: process.nextTick, Promises, Object.observe, MutationObserver
Example
JS code
console.log('main thread start ...');
setTimeout(() => console.log('Timeout1'), 0); // Macro task queue
let promiseF = () => new Promise(resolve => setTimeout(() => resolve('Timeout3'), 0));
let asyncF = async () => console.log(await promiseF());
asyncF(); // For async will wrap the result with promise, "console.log(await promiseF())"" enters Micro task
let p1 = Promise.resolve('p1');
let p2 = Promise.resolve('p2');
p1.then(r => {
console.log(r); // p1
setTimeout(() => console.log('Timeout2'), 0); // Macro task queue
const p3 = Promise.resolve('p3');
p3.then(console.log); // p3
}); // Micro task queue
p2.then(console.log); // p2
setTimeout(() => console.log('Timeout4'), 0); // Macro task
console.log('main thread end.');
上面程序运行的结果如下:
main thread start ...
main thread end.
p1
p2
p3
Timeout1
Timeout4
Timeout2
Timeout3
执行顺序
-
同步代码
- 执行
console.log('main thread start ...');
,然后打印日志main thread start ...
. - 将
() => console.log('Timeout1')
这个回调任务加入macrotask queue. - 调用
asyncF()
函数, 因为该函数是async function, 它会将console.log(await promiseF())
包裹为promise, 可以把它当作promise.then(...)
, 因此console.log(await promiseF())
将会加入到 microtask queue. -
p1.then(...)
和p2.then(...)
也会加入到 microtask queue. - 将
() => console.log('Timeout4')
回调函数加入到macrotask queue. - 执行
console.log('main thread end.');
, 然后打印main thread end.
.
上面的操作完成后, Macrotask queue 与 Microtask queue 将会变成这样:
Macrotask queue Microtask queue ┌─────────────────────────┐ ┌───────────────────────────────────┐ | console.log('Timeout1') | | console.log(await promiseF()) | └─────────────────────────┘ └───────────────────────────────────┘ | console.log('Timeout4') | | p1.then(...) | └─────────────────────────┘ └───────────────────────────────────┘ | p2.then(...) | └───────────────────────────────────┘
- 执行
-
处理microtask队列
- 按照先进先出的原则, 首先处理
console.log(await promiseF())
, 我们可以将await
当作一个microtask, 因此microtask队列看起来如下:
Microtask queue ┌────────────────────────────────────────────┐ | p1.then(...) | └────────────────────────────────────────────┘ | p2.then(...) | └────────────────────────────────────────────┘ | setTimeout(() => resolve('Timeout3'), 0) | └────────────────────────────────────────────┘
- 处理
p1.then(...)
, 打印"p1", 将console.log('Timeout2')
加入到 macrotask queue, 将p3.then(...)
加入到 microtask queue.
Macrotask queue Microtask queue ┌─────────────────────────┐ ┌────────────────────────────────────────────┐ | console.log('Timeout1') | | p2.then(...) | └─────────────────────────┘ └────────────────────────────────────────────┘ | console.log('Timeout4') | | setTimeout(() => resolve('Timeout3'), 0) | └─────────────────────────┘ └────────────────────────────────────────────┘ | console.log('Timeout2') | | p3.then(...) | └─────────────────────────┘ └────────────────────────────────────────────┘
- 处理
p2.then(...)
, 打印p2
, 然后执行setTimeout(() => resolve('Timeout3'), 0)
, 将resolve('Timeout3')
加入 macrotask queue, 然后打印p3
.
Macrotask queue ┌─────────────────────────┐ | console.log('Timeout1') | └─────────────────────────┘ | console.log('Timeout4') | └─────────────────────────┘ | console.log('Timeout2') | └─────────────────────────┘ | resolve('Timeout3') | └─────────────────────────┘
- 最后清空所有macrotask queue(即处理完所有宏任务)。
- 按照先进先出的原则, 首先处理
因此,最终的打印结果是
main thread start ...
main thread end.
p1
p2
p3
Timeout1
Timeout4
Timeout2
Timeout3
网友评论