010Node事件循环
[A] node事件循环的六个阶段:
timers阶段:
执行定时器回调(setTimeout, setInterval)
I/O callback阶段:
处理I/O相关的回调(文件读写,数据库操作,网络请求, ...)
idle, prepare阶段:
近在node内部使用的阶段
poll阶段:
空转/循环阶段,其内部有任务,则会同步执行
checks阶段:
执行setImmediate的回调
close callback阶段:
执行相关事件回调的关闭操作
[B] node中的代码执行机制:
1. 主代码从上往下顺序执行,遇到的异步代码,根据其类型放入对应的队列中等待执行
setTimeout, setInterval -> timers队列
I/O -> I/O队列
setImmediate -> check队列
...
注:
1. 上述,遇到的异步代码,会被首先放在异步模块中,等待其达到回调的条件(定时器到期,网络请求成功响应, ...)
2. 达到条件的回调,才会被放入到事件循环对应的队列中去
3. 例外情况,setImmediate会直接被放入到check队列队列中去(没有先放入异步模块的过程)
2.主代码执行结束,开始进入事件轮询(循环)
进入事件轮训之前,先执行微任务队列:
process.nextTicK队列中的任务
微任务队列中的任务
随后,进入事件循环
从上往下,依次经历:
timer阶段
poll阶段
check阶段
3. 执行过程:
1. 在timer阶段中,若timer队列中有已到期的回调,则按顺序执行所有的回调,
然后向下进入poll阶段
2. 在poll阶段,若poll中存在待执行的I/O回调,则会同步执行所有回调
然后,检查check队列是否有待执行的回调,若有,则进入check阶段
3. 在check阶段,按顺序执行check队列中的所有回调,然后回到poll阶段
poll阶段:
1. poll阶段实际是一个空转等待的过程
此时,是因为整体代码并未完全执行完毕(主代码执行结束,但异步代码还会达到回调条件)
2. poll阶段,
poll阶段两种理解:
理解1:
poll阶段会持续去判断timer队列和check队列中是否存在可执行的任务
若有,则按照顺序执行check队列,再执行timers队列中的所有任务
然后,再回到poll阶段继续等待
理解2:
事件轮训阶段,就一直在timers,poll,checks三个阶段按顺序反复循环,若如到对应阶段的队列中存在待执行任务,则去执行(清空该队列)
两种理解均可,逻辑是一样的
3. poll阶段的结束条件:
1. 异步模块中,不存在尚在等待满足条件的回调,且事件循环各阶段对应的队列均为空,则结束事件循环。
注:这是代码执行结束时的正常停止逻辑
2. poll阶段等待的时间达到硬件规定的最长时间,如:网络请求时间太长
注:这属于强制停止,是一种异常情况,这是不希望看到的
4. 微任务队列
1. 在事件循环中,从timer -> poll -> check 为一次完整的轮询
2. 而事件轮询就是上述轮询重复的过程
3. 而实际上,事件轮询的队列全部为宏任务队列,在轮询中,还涉及到一个微任务队列
4. 代码执行过程中,遇到的微任务,如promise.then等会儿比放入到微任务队列,等待执行
5. 每次事件轮询之前,都会去清空微任务队列,然后才开始下一轮的事件轮询
5. nextTick
1. nextTick是node中独有的一种异步回调方式
2. nextTick的回调会被专门放在nextTick队列中去
3. nextTick的执行优先级高于微任务队列
因此,每次事件轮询前会,先清空nextTick队列,再清空微任务队列,然后在进入下一轮事件轮询