时间循环的流程如下
┌───────────────────────┐
┌─>│ timers │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ I/O callbacks │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ idle, prepare │
│ └──────────┬────────────┘ ┌───────────────┐
│ ┌──────────┴────────────┐ │ incoming: │
│ │ poll │<─────┤ connections, │
│ └──────────┬────────────┘ │ data, etc. │
│ ┌──────────┴────────────┐ └───────────────┘
│ │ check │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
└──┤ close callbacks │
└───────────────────────┘
每个phase都维持着一个FIFO的回调函数队列。
Phases
timers: this phase executes callbacks scheduled by setTimeout() and setInterval().I/O callbacks: executes almost all callbacks with the exception of close callbacks, the ones scheduled by timers, and setImmediate().idle,prepare: only used internally.poll: retrieve new I/O events; node will block here when appropriate.check: setImmediate() callbacks are invoked here.close callbacks: e.g.socket.on('close', ...).
timer
Technically, the poll phase controls when timers are executed.
I/0 callbacks
执行一些系统操作的回调函数,如TCP错误类型。
eg:当尝试连接时,如果一个TCP套接字收到了ECONNREFUSED, 一些linux系统可能希望等待错误报告。这将会在本阶段内入队等待执行。
poll
本阶段有两个主要功能:
- 执行队列里已到期的timer,然后
- 处理poll队列里的事件
如果没有timer调度,将会发生以下两种之一:
- 如果poll队列不为空,则继续同步迭代器队列内的回调函数直到exhausted或是达到系统硬性限制。
- 如果poll队列为空, 将会发生以下两种之一。
- 如果脚本被
setImmediate()调度,时间循环将会结束poll阶段并进入check阶段执行被调度的脚本。 - 如果脚本不是被
setImmediate()调度,事件循环将会等待回调函数加入队列,然后即时之行之。
- 如果脚本被
一旦poll队列为空,时间循环会检查到达threshold的timers。如果一个或多个timer ready了,时间循环将会包装会timer阶段执行timer的回调函数。
check
本阶段允许在poll阶段结束后立即执行callbacks。如果poll阶段转为idle并且被setImmediate()入队,时间循环将直接进入check阶段而不是等待poll事件。
setImmediate()实际上是在事件循环中一个单独的阶段运行的一个特殊的timer。使用一个libuv的api来调度在poll阶段完成之后的callbacks的执行
通常来说,在代码执行过程中,事件循环将最终hit到poll阶段(poll阶段也会等待for incoming connection, request,etc)。但如果一个callback被setImmediate()调度并且poll阶段变为idle,it(poll)将直接结束并进入check阶段而非等待poll事件。
close callbacks
如果一个socket或是handle被突然关闭(e.g. socket.destroy()),close事件将会被emit到本阶段。其他情况将会被emit到process.nextTick()。
setImmediate() vs setTimeout()
这两者很相似,但是行为表现不同 depend on 何时调用。
setImmediate()被设计为在当前poll阶段完成时执行setTimeout()调度一段脚本在达到minimum的threshold时执行
setImmediate()oversetTimeout()的最主要优点在于:
setImmediate()的执行顺序比任何在I/O阶段调度的timer高。
process.nextTick()
任何时候你都可以在一个给定的阶段里调用process.nextTick(),所有传入process.nextTick()的callbacks都会在事件循环continue前resolved.这可以创建一些很不好的解决方案,因为你可以利用递归process.nextTick()手动starve将你的I/O,这种做法将防止事件循环进入poll阶段。
process.nextTick() vs setImmediate()
process.nextTick()在本阶段立即解除setImmediate()事件循环中的接下来的iteration或是tick中解除
Why use process.nextTick()
有两个主要原因:
- 允许使用者处理错误,清初任何不必要的资源,或是可能在下次事件循环时尝试再次请求
- 在下次事件循环前,有时候有必要在调用栈释放后,允许一个callback执行
ps:
- 如果可能的话,调用setTimeout时,尽量使用相同的超时值
- 尽量用process.nextTick 来代替 setTimeout(fn ,0)