经过查找资料和测试,发现现在的node和浏览器下的 宏任务与微任务的执行顺序没有区别
左边是浏览器,右边是node(V12.18.2)
但是,在node之前的版本宏任务/微任务和浏览器的执行顺序确实有差别 ,在把node降到v8.4.0时 ,宏任务要执行完一队列的任务,才会去执行新的微任务,而不是像我们前一篇 说到的,每执行完一个宏任务,就去检测是否有微任务需要执行
下图运行一段超长代码
运行结果:14、15、1、2、4、16、8、8promise、8promise+then、9、5、6、10、11、12、3、7、13
宏任务:setTimeOut、setImmediate,js代码
微任务:promise、process.nextTick
这段代码,我们之前没涉及到的知识点是:
1.微任务里process.nextTick,方便记忆,你可以理解为 它是微任务中的特殊队列,每次执行微任务要先执行这个特殊队列,队列执行完毕再执行其他的微任务。
2.宏任务里的setTimeOut、setImmediate(在浏览器中不存在)的执行顺序。
node中的libuv库负责Node API的执行。它将不同的任务分配给不同的线程,形成一个
Event Loop,以异步的方式将任务的执行结果返回给V8引擎(盗图)
从图中可以看出Node中事件循环的顺序:
外部输入数据–>轮询阶段(poll)–>检查阶段(check)–>关闭事件回调阶段(close callback)–>定时器检测阶段(timer)–>I/O事件回调阶段(I/O callbacks)–>闲置阶段(idle, prepare)–>轮询阶段(按照该顺序反复运行)…
timers阶段: 执行timer(setTimeout 、setInterval)的回调
I/O callbacks阶段: 处理一些上一轮循环中少数未执行的I/O回调
idle,prepare阶段: 仅Node内部使用
poll阶段: 获取新的I/O事件,适当的条件下node将阻塞在这里。(发生阻塞的情况为:poll队列为空,且没有代码设定为setImmediate())
check阶段: 执行setImmediate()的回调。(如果poll阶段空闲,并且有被setImmediate()设定的回调,那么事件循环直接跳到check执行,而不是阻塞在poll阶段等待回调被加入)
[
注意]:setImmediate()在这个阶段具有最高优先级,只要poll队列为空,代码被setImmediate(),无论是否有timers达到下限时间,setImmediate()的代码都先执行。 (下面有例子会解释)
close callbacks阶段:执行socket的close事件回调
setTimeout(() => {
setImmediate(() => {
console.log('setImmediate');
},10);
setTimeout(() => {
console.log('setTimeout');
}, 0);
}, 0);
在timers阶段执行外部的setTimeout之后,内层setTimeout与setImmediate入队,时间循环继续往下走,到poll阶段发现队列为空,此时代码有setImmediate(),所以直接进入check阶段执行setimmediate()的回调。之后再第二次时间循环的timers中再执行相应的回调。
总结:
如果两者都在主模块中调用,那么执行先后取决于进程性能,也就是随机。
如果两者都不在主模块调用(被一个异步操作包裹),那么setImmediate的回调永远先执行。
(继续盗图)
总结
浏览器和Node 环境下,microtask 任务队列的执行时机不同
Node端,microtask 在事件循环的各个阶段之间执行
浏览器端,microtask 在事件循环的 macrotask 执行完之后执行
问题思考(个人见解,期待大佬们的高见)
1、为什么需要区分宏任务和微任务?
个人认为,区分两个队列可以方便插队,或者说可以更加方便的操作异步任务的执行顺序。
2.当所有任务清空之后,setTimeout(,1000000)长时间的循环任务怎么处理?性能怎么样
首先setTimeout定时任务,是不会影响性能的,因为在等待的时间里,它不去占用cpu。等时间到了,就会将它的回调压入异步队列中,等待主线程的提取。所以setTimeout的性能如何取决于它要执行的内容,而不是等待的时间。