首先,libuv引擎封装了底层的实现,比如,libuv实现了io线程池(自定义线程池和IOCP)、事件循环机制;
    首先事件分为异步I/O事件和非异步I/O事件;
    从上图中,大致看出 node 中的事件循环的顺序:
    外部输入数据–>轮询阶段(poll)–>检查阶段(check)–>关闭事件回调阶段(close callback)–>定时器检测阶段(timer)–>I/O 事件回调阶段(I/O callbacks)–>闲置阶段(idle, prepare)–>轮询阶段(按照该顺序反复运行)…

    • timers 阶段:这个阶段执行 timer(setTimeout、setInterval)的回调
    • I/O callbacks 阶段:处理一些上一轮循环中的少数未执行的 I/O 回调
    • idle, prepare 阶段:仅 node 内部使用
    • poll 阶段:获取新的 I/O 事件, 适当的条件下 node 将阻塞在这里
    • check 阶段:执行 setImmediate() 的回调
    • close callbacks 阶段:执行 socket 的 close 事件回调

    image.png
    接下去我们详细介绍timerspollcheck这 3 个阶段,因为日常开发中的绝大部分异步任务都是在这 3 个阶段处理的。
    (1) timer
    timers 阶段会执行 setTimeout 和 setInterval 回调,并且是由 poll 阶段控制的。
    同样,在 Node 中定时器指定的时间也不是准确时间,只能是尽快执行
    (2) poll
    poll 是一个至关重要的阶段,这一阶段中,系统会做两件事情

    • 回到 timer 阶段执行回调
    • 执行 I/O 回调

    并且在进入该阶段时如果没有设定了 timer 的话,会发生以下两件事情

    • 如果 poll 队列不为空,会遍历回调队列并同步执行,直到队列为空或者达到系统限制
    • 如果 poll 队列为空时,会有两件事发生
      • 如果有 setImmediate 回调需要执行,poll 阶段会停止并且进入到 check 阶段执行回调
      • 如果没有 setImmediate 回调需要执行,会等待回调被加入到队列中并立即执行回调,这里同样会有个超时时间设置防止一直等待下去

    当然设定了 timer 的话且 poll 队列为空,则会判断是否有 timer 超时,如果有的话会回到 timer 阶段执行回调。
    (3) check 阶段
    setImmediate()的回调会被加入 check 队列中,从 event loop 的阶段图可以知道,check 阶段的执行顺序在 poll 阶段之后。
    我们先来看个例子:

    1. console.log('start')
    2. setTimeout(() => {
    3. console.log('timer1')
    4. Promise.resolve().then(function() {
    5. console.log('promise1')
    6. })
    7. }, 0)
    8. setTimeout(() => {
    9. console.log('timer2')
    10. Promise.resolve().then(function() {
    11. console.log('promise2')
    12. })
    13. }, 0)
    14. Promise.resolve().then(function() {
    15. console.log('promise3')
    16. })
    17. console.log('end')
    18. //start=>end=>promise3=>timer1=>promise1=>timer2=>promise2
    • 一开始执行栈的同步任务(这属于宏任务)执行完毕后(依次打印出 start end,并将 2 个 timer 依次放入 timer 队列),会先去执行微任务(这点跟浏览器端的一样),所以打印出 promise3
    • 然后进入 timers 阶段,执行 timer1 的回调函数,打印 timer1,并将 promise.then 回调放入 microtask 队列,同样的步骤执行 timer2,打印 timer2;这点跟浏览器端相差比较大,timers 阶段有几个 setTimeout/setInterval 都会依次执行,并不像浏览器端,每执行一个宏任务后就去执行一个微任务(关于 Node 与浏览器的 Event Loop 差异,下文还会详细介绍)。