node定时器简介
node的定时器共有四种,分别是:
setTimeout()
setInterval()
setImmediate()
process.nextTick()
前两个是语言的标准,后两个是node独有的。
node的异步任务可以分为两种,分别是追加在 本轮循环 的异步任务和追加在 次轮循坏 的异步任务。本轮循环一定早于次轮循坏执行。
node规定,process.nextTick
和Promise
的回调函数,追加在本轮循环,即同步任务一旦执行完成就开始执行他们。而setTimeout
、setInterval
、setImmediate
的回调函数追加在次轮循坏。
process.nextTick & 微任务
process.nextTick
是在本轮循环执行的,而且是所有异步任务里面最快执行的,如果希望异步任务尽可能快地执行那就使用它。
根据语言规格,Promise
对象的回调函数,会进入异步任务里面的“微任务”(microtask)队列。
微任务队列追加在process.nextTick队列的后面,也属于本轮循环。
注:只有前一个队列全部清空以后,才会执行下一个队列,而且process.nextTick的回调函数,执行都会早于Promise的。
事件循环的概念
首先,node只有一个主线程,事件循环是在主线程上完成的。
其次,Node 开始执行脚本时,会先进行事件循环的初始化,但是这时事件循环还没有开始,会先完成同步任务、发出异步请求、规划定时器生效的时间、执行process.nextTick()等等事情,然后才开始事件循环。事件循环会无限次地执行,一轮又一轮。只有异步任务的回调函数队列清空了,才会停止执行。每一轮的事件循环,分成六个阶段,这些阶段会依次执行,如下图。每个阶段都有一个先进先出的回调函数队列。只有一个阶段的回调函数队列清空了,该执行的回调函数都执行了,事件循环才会进入下一个阶段。
关于 setTimeout 和 setImmediate 执行顺序
一般来说,由于setTimeout
在 timers 阶段执行,而setImmediate
在 check 阶段执行。所以,setTimeout
会早于setImmediate
完成。但也可能存在一些特殊情况是的setImmediate
早于setTimeout
完成,例如
setTimeout(()=> console.log(1));setImmediate(()=> console.log(2));
上面代码应该先输出1,再输出2,但是实际执行的时候,结果却是不确定,有时还会先输出2,再输出1。
这是因为setTimeout的第二个参数默认为0。但是实际上,Node 做不到0毫秒,最少也需要1毫秒,根据官方文档,第二个参数的取值范围在1毫秒到2147483647毫秒之间。也就是说,setTimeout(f, 0)等同于setTimeout(f, 1)。
实际执行的时候,进入事件循环以后,有可能到了1毫秒,也可能还没到1毫秒,取决于系统当时的状况。如果没到1毫秒,那么 timers 阶段就会跳过,进入 check 阶段,先执行setImmediate的回调函数。
但是,下面的代码一定是先输出2,再输出1。
const fs =require('fs');
fs.readFile('test.js',()=>{setTimeout(()=> console.log(1));setImmediate(()=> console.log(2));});
上面代码会先进入I/O callbacks
阶段,然后是check
阶段,最后才是timers
阶段。因此,setImmediate
才会早于setTimeout
执行。
总结
基于此,node定时器的执行顺序大致为:同步任务->process.nextTick->promise->setTimeout->setImmediate
setTimeout(()=> console.log(1));
setImmediate(()=> console.log(2));
process.nextTick(()=> console.log(3));
Promise.resolve().then(()=> console.log(4));
(()=> console.log(5))();
process.nextTick(()=> console.log(6));
因此,上述例子输出为 5,3,6,4,1,2
参考链接:Node定时器详解-阮一峰的网络日志