首先让我们先看一下Node的时间循环阶段,完整的事件循环Tick是分成多个阶段的:
Node的事件循环阶段
- 定时器:本阶段执行已经被setTimeout()和setInterval()的调度回调函数
- 待定回调(I/O callback):对某些系统操作执行回调,比如TCP连接时接收到ECONNREFUSED
- idle,prepare:仅系统内部使用
- 轮询(poll):检索新的I/O事件,执行与I/O相关的回调
- 检测:setImmediate()回调函数在这里执行
- 关闭的回调函数:一些关闭的回调函数,如:socket.on(’close’,…)
事件循环中setTimeout()和setImmediate的执行顺序
setTimeout(() => {
console.log('setTimeout')
}, 0)
setImmediate(() => {
console.log('setImmediate')
})
- 在node中,setTimeout(demo,0)===setTimeout(demo,1)
- 在浏览器中setTimeout(demo,0)===setTimeout(demo,4)
- 因为event loop的启动也是需要时间的,可能执行到poll阶段已经超过1ms,此时setTimeout就先执行。反之setImmediate先执行
出现这种情况的原因
- 在node的源码中deps/uv/timer.c的地141行中,有一个uv_next_timeout的函数
- 这个函数决定了poll阶段要不要阻塞在这里
- 阻塞在这里的目的是当有异步IO被处理时,尽可能快的让代码被执行
- 所以就会出现两种情况
- 情况一:如果时间循环开启的时间是小于setTimeout函数的执行时间的;
- 就意味着先开启了event-loop,但是这个时候执行到timer阶段,并没有定时器的回调被放到timer queue中;所以没有被执行,后续开启定时器和检测到有setImmediate时,就会跳过poll阶段,向后继续执行;这个时候是想检测setImmediate,第二次tick中执行timer中的setTimeout
- 情况二:如果时间循环开启的时间是大于setTimmeout函数的执行时间的;
- 这就意味着在第一次tick中,已经准备好timer queue;所以会直接按照顺序执行即可。
- 情况一:如果时间循环开启的时间是小于setTimeout函数的执行时间的;