当 node
执行完主线程中的同步任务时, 它会检查是否有等待中的异步操作, 如果有,则会开始事件循环, 否则它会退出.
事件循环的 6 个阶段
timer
- 执行由
setTimeout
和setInterval
设置的回调
- 执行由
pending callbacks
- 执行被延迟的
I/O
回调
- 执行被延迟的
idle, prepare
- 内部操作
poll
- 获取新的
I/O
事件(eg.connection
、request
), 执行I/O
相关回调(除了close callbacks
、timer
、check
)
- 获取新的
check
- 执行由
setImmediate
设置的回调
- 执行由
close callbacks
- 执行一些关闭操作回调(eg.
socket.destroy
)
- 执行一些关闭操作回调(eg.
这些异步操作, 可在入口脚本中被添加至指定的模块(eg. 定时器模块), eg:
//entry.js
console.log(1);
//异步操作, 就绪时, 其回调被添加进 timer 队列中
setTimeout(()=>console.log('timeout callback'),0);
console.log(2);
//异步操作, 其回调被添加进 check 队列中
setImmediate(()=>console.log('immediate callback'));
console.log(3);
当某个异步操作被就绪时(eg. 定时器到达指定时间), 该异步操作的回调就会被添加到相应阶段的队列中(eg. 定时器的回调被添加到 timer
阶段的队列中),等待被执行.
执行顺序
node
按照上图的顺序执行每个阶段的队列, 当队列为空时, 它会转入下一阶段. 当执行 poll
阶段时, 它按照以下逻辑执行:
- 当
poll
的队列不为空时, 它会执行其队列中的回调, 直到其为空或者达到系统的限制 当
poll
的队列为空且没有定时器就绪时- 如果有
setImmediate
就绪, 则会结束poll
阶段, 转入check
阶段 - 计算距离下一个定时器就绪的时间, 在此之前,停在
poll
阶段, 等待回调被加入其队列
- 如果有
当 poll
执行完其队列后, 会检查是否有定时器就绪, 如果有, 则会结束 poll
阶段, 继续本轮循环的执行, 然后进入下一轮循环中执行就绪的定时器.
setTimeout 与 setImmediate
当在非 I/O
周期中执行 setTimeout
和 setImmediate
时, 其顺序依赖于当前系统的环境. 如果系统运行到事件循环开始, 超过了定时器设定的时间( setTimeout
的第二个参数默认值为 0
, 但是 node
达不到 0ms
就执行定时器的要求, 实际值从 1ms
开始), 则 setTimeout
先于 setImmediate
被执行, 反之 setImmediate
先于 setTimeout
.
setTimeout(()=>console.log('timeout'),0);
setImmediate(()=>console.log('immediate'));
//output: 1 2 or 2 1
当在 I/O
周期中执行时, setImmediate
先于 setTimeout
执行. 因为执行 I/O
操作时为 poll
阶段, poll
阶段结束后, 就转入了 check
阶段.
const fs = require('fs');
fs.readFile(__filename,()=>{
setTimeout(()=>console.log('timeout'),0);
setImmediate(()=>console.log('immediate'));
})
//output: immediate timeout
process.nextTick
process.nextTick
在所有同步操作执行完毕之后、事件循环开始之前执行、每个阶段执行之前执行
setTimeout(()=>console.log('timeout'),0);
process.nextTick(()=>console.log('nextTick'));
console.log('entry');
//output: entry nextTick timeout
setTimeout(()=>{
console.log('timeout1');
process.nextTick(()=>console.log('nextTick'));
},0);
setTimeout(()=>console.log('timeout2'));
//output: timeout1 timeout2 nextTick
setTimeout(()=>{
console.log('wrap-timeout');
setTimeout(()=>console.log('inner-timeout'),0);
process.nextTick(()=>{
console.log('wrap-nextTick');
process.nextTick(()=>console.log('inner-nextTick'));
});
},0);
//output: wrap-timeout wrap-nextTick inner-nextTick inner-timeout