上图需要注意以下几点:
一个Event Loop可以有一个或多个事件队列,但是只有一个微任务队列。
微任务队列全部执行完会重新渲染一次
每个宏任务执行完都会重新渲染一次
requestAnimationFrame处于渲染阶段,不在微任务队列,也不在宏任务队列
所以想要知道一个异步API在哪个阶段执行,我们得知道他是宏任务还是微任务。
常见宏任务有:
script (可以理解为外层同步代码)
setTimeout/setInterval
setImmediate(Node.js)
I/O
UI事件
postMessage
常见微任务有:
Promise
process.nextTick(Node.js)
Object.observe
MutaionObserver
上面这些事件类型中要注意Promise,他是微任务,也就是说他会在定时器前面运行,我们来看个例子:
console.log('1'); setTimeout(() => { console.log('2'); },0); Promise.resolve().then(() => { console.log('5'); }) new Promise((resolve) => { console.log('3'); resolve(); }).then(() => { console.log('4'); })上述代码的输出是1,3,5,4,2。因为:
先输出1,这个没什么说的,同步代码最先执行
console.log('2');在setTimeout里面,setTimeout是宏任务,“2”进入宏任务队列
console.log('5');在Promise.then里面,进入微任务队列
console.log('3');在Promise构造函数的参数里面,这其实是同步代码,直接输出
console.log('4');在then里面,他会进入微任务队列,检查事件队列时先执行微任务
同步代码运行结果是“1,3”
然后检查微任务队列,输出“5,4”
最后执行宏任务队列,输出“2”
Node.js的Event LoopNode.js是运行在服务端的js,虽然他也用到了V8引擎,但是他的服务目的和环境不同,导致了他API与原生JS有些区别,他的Event Loop还要处理一些I/O,比如新的网络连接等,所以与浏览器Event Loop也是不一样的。Node的Event Loop是分阶段的,如下图所示:
timers: 执行setTimeout和setInterval的回调
pending callbacks: 执行延迟到下一个循环迭代的 I/O 回调
idle, prepare: 仅系统内部使用
poll: 检索新的 I/O 事件;执行与 I/O 相关的回调。事实上除了其他几个阶段处理的事情,其他几乎所有的异步都在这个阶段处理。
check: setImmediate在这里执行
close callbacks: 一些关闭的回调函数,如:socket.on('close', ...)
每个阶段都有一个自己的先进先出的队列,只有当这个队列的事件执行完或者达到该阶段的上限时,才会进入下一个阶段。在每次事件循环之间,Node.js都会检查它是否在等待任何一个I/O或者定时器,如果没有的话,程序就关闭退出了。我们的直观感受就是,如果一个Node程序只有同步代码,你在控制台运行完后,他就自己退出了。
还有个需要注意的是poll阶段,他后面并不一定每次都是check阶段,poll队列执行完后,如果没有setImmediate但是有定时器到期,他会绕回去执行定时器阶段:
setImmediate和setTimeout上面的这个流程说简单点就是在一个异步流程里,setImmediate会比定时器先执行,我们写点代码来试试:
console.log('outer'); setTimeout(() => { setTimeout(() => { console.log('setTimeout'); }, 0); setImmediate(() => { console.log('setImmediate'); }); }, 0);上述代码运行如下:
和我们前面讲的一样,setImmediate先执行了。我们来理一下这个流程:
外层是一个setTimeout,所以执行他的回调的时候已经在timers阶段了
处理里面的setTimeout,因为本次循环的timers正在执行,所以他的回调其实加到了下个timers阶段
处理里面的setImmediate,将它的回调加入check阶段的队列
外层timers阶段执行完,进入pending callbacks,idle, prepare,poll,这几个队列都是空的,所以继续往下
到了check阶段,发现了setImmediate的回调,拿出来执行
然后是close callbacks,队列时空的,跳过
又是timers阶段,执行我们的console