@
nikolausliu 哈喽,我这边查了 nodejs 的官方英文文档。关于 eventloop 执行流程,我这边的理解如下。首先是 timer 阶段,执行 timer queue 中的函数。然后是 sys pending 阶段,就不多介绍了,主要是执行系统回调。然后是最重要的 polling 阶段,该阶段的两个主要功能就是一根据 timer 所指定的时间阈值和 io 计算 polling 时间,二执行 poling queue 中出现的 io 等回调函数。polling 的流程文档有部分逻辑描述不是很清晰,大致如下,在没有 timer 的情况下,判断队列是否为空,则阻塞等待;如果不为空,则执行其中的值。当执行完毕时,也就是队列为空时,再次判断所执行的脚本中是否有 timer,有 immediate timer 则进入 check 阶段执行,有到期的 timeout timer 则重回 timer 阶段执行。至此 polling 阶段结束。后面的 check 和 close 阶段不多介绍。
那你遇到的问题可以这样解释,程序进入主程序执行,初始化事件循环。timer 放入 timer queue,发起 file io 注册回调,for 循环阻塞进程,immediate 放入 immediate queue。主程序执行完毕,进入事件循环。timer 中执行已到期函数(这里因为执行主程序时阻塞了一段时间,所以 timer 肯定到期了。不然这里就存在 timer 到没到期,影响 immediate 与 timer 的输出顺序),输出 timer,然后 immediate 中有值,执行。然后等待 io 或执行 polling queue。最后再次去 immediate 和 timer 中判断是否有待执行函数。所以输出顺序是 timer immediate io.
这也和官方文档中的示例所吻合,在 setTimeout 中再设置一个 timer 和 immediate,timer 阶段 parent timer 执行,进入 polling 时会根据 childtimer 来计算时间,此时没有 io script 执行,所以 timer 有可能执行的很快到期了,也有可能没有到期,然后进入 check 阶段执行。所以出现了随机顺序的输出。官网中的 io polling 中有 script 设置了 immediate,所以稳定先输出 immediate,而无论 timer 是否到期了都是第二轮以后的 tick 中的 timer 执行。
所以上述两个 demo 程序的分歧点就在于,进入后 polling 最开始的逻辑是否要检测 immediate 是否存在。根据结果来说,第一次 tick 需要检测并执行,后面 tick 不需要。根据有些文章说明,底层的 libuv 确实存在三种 tick 类型的区别。这边因为小的对 C 的知识也不太熟悉,只能后续再求解了。
附上官方文档,
https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/写的有点多了,后续整理成文章。有没说清,或理解不正确的地方,还请多多指教。