浏览器Event Loop执行流程
事件循环其实就是入栈出栈的循环。上面例子中说到了setTimeout,那setInterval呢,Promise呢等等等等,有很多异步的函数。但是这些异步任务有分宏任务(macro-task)和微任务(micro-task): macro-task包括: setTimeout, setInterval, setImmediate, I/O, UI rendering。 micro-task包括:process.nextTick, Promises, Object.observe, MutationObserver。 每一次Event Loop触发时:
- 执行完主执行线程中的任务。
- 取出micro-task中任务执行直到清空。
- 取出macro-task中一个任务执行。
- 取出micro-task中任务执行直到清空。
- 重复3和4。
Node Event Loop执行流程
js执行为单线程,所有代码皆在主线程调用栈完成执行。当主线程任务清空后才会去轮询取任务队列中任务。
循环阶段
在node中事件每一轮循环按照顺序分为6个阶段,来自libuv的实现:
- timers:执行满足条件的setTimeout、setInterval回调。
- I/O callbacks:是否有已完成的I/O操作的回调函数,来自上一轮的poll残留。
- idle,prepare:可忽略
- poll:等待还没完成的I/O事件,会因timers和超时时间等结束等待。
- check:执行setImmediate的回调。
- close callbacks:关闭所有的closing handles,一些onclose事件。
几个队列
除上述循环阶段中的任务类型,我们还剩下浏览器和node共有的microtask和node独有的process.nextTick
,我们称之为Microtask Queue和NextTick Queue。
我们把循环中的几个阶段的执行队列也分别称为Timers Queue、I/O Queue、Check Queue、Close Queue。
循环之前
在进入第一次循环之前,会先进行如下操作:
- 同步任务
- 发出异步请求
- 规划定时器生效的时间
- 执行
process.nextTick()
开始循环
按照我们的循环的6个阶段依次执行,每次拿出当前阶段中的全部任务执行,清空NextTick Queue,清空Microtask Queue。再执行下一阶段,全部6个阶段执行完毕后,进入下轮循环。即:
- 清空当前循环内的Timers Queue,清空NextTick Queue,清空Microtask Queue。
- 清空当前循环内的I/O Queue,清空NextTick Queue,清空Microtask Queue。
- 清空当前循环内的Check Queu,清空NextTick Queue,清空Microtask Queue。
- 清空当前循环内的Close Queu,清空NextTick Queue,清空Microtask Queue。
- 进入下轮循环。
可以看出,nextTick
优先级比promise
等microtask高。setTimeout
和setInterval
优先级比setImmediate
高。
注意
- 如果在timers阶段执行时创建了
setImmediate
则会在此轮循环的check阶段执行,如果在timers阶段创建了setTimeout
,由于timers已取出完毕,则会进入下轮循环,check阶段创建timers任务同理。 -
setTimeout
优先级比setImmediate
高,但是由于setTimeout(fn,0)
的真正延迟不可能完全为0秒,可能出现先创建的setTimeout(fn,0)
而比setImmediate
的回调后执行的情况。
伪代码
while (true) {
loop.forEach((阶段) => {
阶段全部任务()
nextTick全部任务()
microTask全部任务()
})
loop = loop.next
}
实际例子
setImmediate(function(){
console.log(1);
},0);
setTimeout(function(){
console.log(2);
},0);
new Promise(function(resolve){
console.log(3);
resolve();
console.log(4);
}).then(function(){
console.log(5);
});
console.log(6);
process.nextTick(function(){
console.log(7);
});
console.log(8);
复制代码结果是:3 4 6 8 7 5 2 1
优先级关系如下:
process.nextTick > promise.then > setTimeout > setImmediate
V8实现中,两个队列各包含不同的任务:
macrotasks: script(整体代码),setTimeout, setInterval, setImmediate, I/O, UI rendering
microtasks: process.nextTick, Promises, Object.observe, MutationObserver
参考
- https://juejin.im/post/5aa5dcabf265da239c7afe1e