Appearance
事件循环
┌───────────────────────────┐ ┌─>│ timers │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ pending callbacks │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ idle, prepare │ │ └─────────────┬─────────────┘ ┌───────────────┐ │ ┌─────────────┴─────────────┐ │ incoming: │ │ │ poll │<─────┤ connections, │ │ └─────────────┬─────────────┘ │ data, etc. │ │ ┌─────────────┴─────────────┐ └───────────────┘ │ │ check │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ └──┤ close callbacks │ └───────────────────────────┘
timers:存放 setTimeout 和 setInterval 的回调。 pending callbacks:执行延迟到下一个循环迭代的 I/O 回调。 idle, prepare:仅系统内部使用。 poll:主要存放异步 I/O 操作,Node 中基本所有的异步 api 的回调都在这个阶段来处理。 check:存放 setImmediate 的回调。 close callbacks:一些关闭的回调函数,如:socket.on('close', ...)。
这些队列中存放的都是宏任务,同步代码执行完后,从上到下清空每个队列,每执行完一个宏任务 之后都会清空所有微任务,清空完 poll 后,如果 check 队列有宏任务就去执行,没有就在 poll 等着,等到 timers 队列有新的宏任务就去清空宏任务,不断循环。老版本(< 10)中是每清空完 一个队列后才执行微任务。
所有传递到 process.nextTick() 的回调将在事件循环继续之前解析。可以理解为同步代码 执行完后先执行 nextTick 回调,再执清空队列,再进行循环。
js
setTimeout(() => {
console.log('timeout')
}, 0);
Promise.resolve().then(() => {
console.log('then')
})
process.nextTick(() => {
console.log('nextTick')
}) // 优先级高于微任务结果不确定,受性能影响,如果主栈一下执行完了,马上清空 timer 队列,而定时器回调还没能 放进去,导致 setImmediate 回调先放进去了。
js
setTimeout(() => {
console.log('timeout')
}, 0);
setImmediate(() => {
console.log('immediate')
});js
setImmediate(() => {
console.log('immediate')
});
setTimeout(() => {
console.log('timeout')
}, 0);输出顺序始终为 immediate timeout 先执行 poll 队列,内部把宏任务放到各自的队列,执行完后,先看 check 队列,发现有 宏任务并清空,之后进入 timer 队列。
js
const fs = require('fs')
fs.readFile('./1-介绍.md', () => { // poll
setTimeout(() => { // timer
console.log('timeout')
}, 0);
setImmediate(() => { // check
console.log('immediate')
});
})