Node.js 的事件循环流程大体以下:javascript
┌───────────────────────────┐
┌─>│ timers │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ pending callbacks │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ idle, prepare │
│ └─────────────┬─────────────┘ ┌───────────────┐
│ ┌─────────────┴─────────────┐ │ incoming: │
│ │ poll │<─────┤ connections, │
│ └─────────────┬─────────────┘ │ data, etc. │
│ ┌─────────────┴─────────────┐ └───────────────┘
│ │ check │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
└──┤ close callbacks │
└───────────────────────────┘
复制代码
每一个阶段都有本身的任务队列,当本阶段的任务队列都执行完毕,或者达到了执行的最大任务数,就会进入到下一个阶段。java
这个阶段会执行被 setTimeout
和 setInterval
设置的定时任务。 固然,这个定时并非准确的,而是在超过了定时时间后,一旦获得执行机会,就马上执行。node
这个阶段会执行一些和底层系统有关的操做,例如TCP链接返回的错误等。这些错误发生时,会被Node 推迟到下一个循环中执行。git
这个阶段是用来执行和 IO 操做有关的回调的,Node会向操做系统询问是否有新的 IO 事件已经触发,而后会执行响应的事件回调。几乎全部除了 定时器事件、 setImmediate()
和 close callbacks
以外操做都会在这个阶段执行。github
这个阶段会执行 setImmediate()
设置的任务。markdown
若是一个 socket
或 handle(句柄)
忽然被关闭了,例如经过 socket.destroy()
关闭了,close
事件将会在这个阶段发出。网络
事件循环初始化以后,会按照上图所示的流程进行:socket
pending callback
回调;idle
、 prepare
阶段,这里会执行 Node 内部的一些逻辑;poll
轮询阶段。在这个阶段会执行全部的 IO 回调,如 读取文件,网络操做等。 poll
阶段有一个 poll queue
任务队列。这个阶段的执行过程相对较长,具体以下:timeout
定时队列是否有可执行的任务,若是有,会跳转到 定时器阶段
执行。定时器任务
,就会检查 poll queue
任务队列,若是不为空,会遍历执行全部任务直到都执行完毕或者达到能执行的最大的任务数量。poll queue
任务队列执行完成后,会检查 setImmediate
任务队列是否有任务,若是有的话,事件循环会转移到下一个 check
阶段。setImmediate
任务,那么,Node 将会在此等待,等待新的 IO 回调的到来,并马上执行他们。 注意 :这个等待不会一直等待下去,而是达到一个限定条件以后,继续转到下一个阶段去执行。setTimeout()
和 setImmediate()
其实也不算秘密,只是我是在刚刚查阅资料才知道的。 那就是:在 Node 中,setTimeout(callback, 0)
会被转换为 setTimeout(callback, 1)
。 详情请参考 这里 。oop
setTimeout()
和 setImmediate()
的执行顺序下面这两个定时任务执行的顺序在不一样状况下,表现不一致。ui
setTimeout(function() { console.log('timeout'); }, 0); setImmediate(function() { console.log('immediate'); }); 复制代码
若是在普通的代码执行阶段(例如在最外层代码块中),设置这两个定时任务,他们的执行顺序是不固定的。
setTimeout(callback, 0)
已经被转换成为 setTimeout(callback, 1)
,因此进入 定时器
阶段时,会根据当前时间判判定时是否超过了 1ms
。1ms
,immediate
任务会先执行;若是存在延迟,而且这个时间达到了 1ms
的界限, timeout
任务就会首先执行。若是咱们在 IO 回调中设置了这两个定时器,那么 setImmediate
任务会首先执行,缘由以下:
poll phase
轮询阶段以前会先检查是否有 timer
定时任务。timer
定时任务,才会执行后面的 IO 回调。setTimeout
定时任务,这时已通过了 timer
检查阶段,因此 timer
定时任务会被推迟到下一个循环中执行。process.nextTick()
不管在事件循环的哪一个阶段,只要使用 process.nextTick()
添加了回调任务,Node 都会在进入下一阶段以前把 nextTickQueue
队列中的任务执行完。
setTimeout(function() { setImmediate(() => { console.log('immediate'); }); process.nextTick(() => { console.log('nextTick'); }); }, 0); // nextTick // immediate 复制代码
上述代码中,老是先执行 nextTick
任务,就是由于在循环在进入下一个阶段以前会先执行 nextTickQueue
中的任务。下面代码的执行结果也符合预期。
setImmediate(() => { setTimeout(() => { console.log('timeout'); }, 0); process.nextTick(() => { console.log('nextTick'); }); }); // nextTick // timeout 复制代码