在node中,大部分介绍的都是异步的I/O,但是在Node中还存在一种与I/O无关的异步API,这些其实也值得我们去关注,它们分别是setTimeout()setInterval()、process.nextTick()和setImmediate()
**

一、定时器

setTimeout()和setInterval()分别用于单次和多次定时执行任务。调用setTimeout()或者setInterval()创建的定时器会被插入到定时器观察者内部的一个红黑树上。每次Tick执行的时候,会从红黑树上迭代取出定时器对象,检查时间是否超过定时时间,若超过就会触发回掉函数理解执行。这里我们以setTimeout为例

image.png

setTimeout的行为
定时器的问题在于它并非精确的。尽管事件循环很快,但当某一次的循环占用的时间比较多,那么下次进入循环可能就会超时了。例如setTimeout()设定一个任务在10ms种后执行,但是9ms后有一个任务占用了5ms的cpu时间片,再次轮到定时器执行的时候,就会超时4ms.

二、process.nextTick()

有时候我们可能需要创建一个立即异步执行的任务,使用定时器,我们可以写成这样来达到效果

  1. setTimeout(function(){
  2. //to do
  3. }, 0)

这里将时间设置为0,但是上面讲过由于事件循环的特点,定时器的精度不够。而且定时器需要动用红黑树,创建定时器对象和迭代等操作,setTimeout(fn, 0)就比较浪费性能。而process.nextTick()方法的操作就很轻量。
每次调用process.nextTick()方法,就会将回调函数放到队列种,再下一轮的Tick时取出执行。定时器种采用红黑树的操作时间复杂度0(log(n)),nextTick()时间复杂度为O(1)。很明显process.nextTick()更加高效。

  1. process.nextTick(() => {
  2. // to do
  3. })

三、setImmediate()

setImmediate()process.nextTick()的方法很类似,都是将回调函数延迟执行。但是两者还是有区别的

  1. setImmediate(() => {
  2. console.log('setImmediate 延迟执行')
  3. })
  4. process.nextTick(() => {
  5. console.log('nextTick 延迟执行')
  6. })
  7. console.log('正常执行')

执行结果如下

image.png

从结果种我们知道,process.nextTick()中的回调函数执行的优先级高于setImmdiate,这里的原因是事件循环对观察者检查有先后的顺序,process.nextTick()属于idle观察者,setImmediate()属于check观察者。在每一轮循环检查中,idle观察者是先于I/O观察者,I/O观察者先于check观察者。

  1. process.nextTick(() => {
  2. console.log('nextTick 延迟执行1')
  3. })
  4. process.nextTick(() => {
  5. console.log('nextTick 延迟执行2')
  6. })
  7. setImmediate(() => {
  8. console.log('setImmediate 延迟执行1')
  9. process.nextTick(() => {
  10. console.log('nextTick 延迟执行3')
  11. })
  12. })
  13. setImmediate(() => {
  14. console.log('setImmediate 延迟执行2')
  15. })
  16. console.log('正常执行')

执行结果如下

image.png