node10.0 版本之前的 Eventloop

先来介绍一下 nodejs 各个阶段执行的任务

  • timers 阶段:执行 setTimeout 和 setInterval 预定的 callback
  • I/O callback:执行除了 close 事件的 callback、被 timers 预定的 callbacks、setImmedia()设定的 callbacks 这些除外的 callbacks
  • idle,prepare:仅 node 内部使用
  • poll 阶段:获取新的 I/O 事件,适当的条件下 node 将阻塞在这里
  • check 解阶段:执行 setImmedia()设定的 callbacks
  • close callback 阶段:执行 socket.on(‘close’)这些 callbacks

nodejs 中的宏任务与微任务

宏任务包括

  • Timers Queue,例如 setTimeout,setInterval
  • IO Callbacks Queue,例如 readFile 等
  • Check Queue,例如 setImmedia
  • Close Callbacks Queue,例如 socket.close

微任务队列包括

  • 1.Next Tick Queue:是放置 process.next/tick(callback)回调任务的
  • 2.Other MicroQueue:放置其他 callbacks,比如 Promise

掘金神图
img

执行过程

结合神图,我们可以看到 nodejs 中 Eeventloop 的过程

  • 1.执行全局的同步代码
  • 2.执行 microtask 微任务,先执行所有 Next Tick Queue 中所有任务,在执行 Other Microtask Queue 中的所有任务
  • 3.开始执行宏任务,注意,nodejs 里面与浏览器里不同,nodejs 里执行到宏任务会全部执行同一轮宏任务,为不会想浏览器那样,从宏任务队列里取一个来执行
  • 4.执行顺序如上图所示:Next Tick Queue —> Other Miora Queue —> Timers Queue —> IO Callback —> Check Queue —> Close Callback Queue

nodejs10.0 之后的 EventLoop

nodejs 官方也有说明,EventLoop 与浏览器的 EventLoop 更为贴近

  • 同步的代码执行
  • 微任务队列
  • 宏任务队列中的单个宏任务执行
  • 再次从同步任务队列—微任务队列—宏任务队列中的单个宏任务执行

一道经典题

console.log('start');

setTimeout(() => {          // callback1
  console.log(111);
  setTimeout(() => {        // callback2
    console.log(222);
  }, 0);
  setImmedia(() => {      // callback3
    console.log(333);
  })
  process.nextTick(() => {  // callback4
    console.log(444);
  })
}, 0);

setImmedia(() => {        // callback5
  console.log(555);
  process.nextTick(() => {  // callback6
    console.log(666);
  })
})

setTimeout(() => {          // callback7
  console.log(777);
  process.nextTick(() => {  // callback8
    console.log(888);
  })
}, 0);

process.nextTick(() => {    // callback9
  console.log(999);
})

console.log('end');

如果我们安装的是 node10.0 之前版本,控制台输出,我们会看到答案是这样的
start->end->999->111->777->444->888->555->333->666->222
如果我们用 node10.0 及以上看到的答案
start->end->999->111->444->777->888->666->666->333->222

为什么在最后的 Timer 任务中,setImmedia 要早于 setTimeout 呢?
因为 setImmedia 的回调顺序不是一定的,这要看 setImmedia 所处的队列,setImmedia 在 Time 和 IO 中,回调要先于 Timer Task

为什么执行次数多了,会发现最外层同级的 setImmedia 也早于 setTimeout?
因为 js 中,做不到真正的 0ms 延迟,所以偶尔会看到 setImmedia 早于 setTimeout,不过客观上,setImmedia 在没有处于 Timer Queue 或者 IO Callbacks Queue 中时,会晚于 setTimeout

以上提到的 setTimeout 等,皆是 0ms 返回,用于对比我对 EventLoop 的简浅认知

0