setTimeout和setImmediate到底谁先执行,本文让你彻底理解Event Loop (3)

image-20200322201434386

上图需要注意以下几点:

一个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 Loop

Node.js是运行在服务端的js,虽然他也用到了V8引擎,但是他的服务目的和环境不同,导致了他API与原生JS有些区别,他的Event Loop还要处理一些I/O,比如新的网络连接等,所以与浏览器Event Loop也是不一样的。Node的Event Loop是分阶段的,如下图所示:

image-20200322203318743

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但是有定时器到期,他会绕回去执行定时器阶段:

image-20200322205308151

setImmediate和setTimeout

上面的这个流程说简单点就是在一个异步流程里,setImmediate会比定时器先执行,我们写点代码来试试:

console.log('outer'); setTimeout(() => { setTimeout(() => { console.log('setTimeout'); }, 0); setImmediate(() => { console.log('setImmediate'); }); }, 0);

上述代码运行如下:

image-20200322210304757

和我们前面讲的一样,setImmediate先执行了。我们来理一下这个流程:

外层是一个setTimeout,所以执行他的回调的时候已经在timers阶段了

处理里面的setTimeout,因为本次循环的timers正在执行,所以他的回调其实加到了下个timers阶段

处理里面的setImmediate,将它的回调加入check阶段的队列

外层timers阶段执行完,进入pending callbacks,idle, prepare,poll,这几个队列都是空的,所以继续往下

到了check阶段,发现了setImmediate的回调,拿出来执行

然后是close callbacks,队列时空的,跳过

又是timers阶段,执行我们的console

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wppszw.html