宏任务与微任务
什么是宏任务和微任务
上面的循环只是一个宏观的表述,实际上异步任务之间也是有不同的,分为
宏任务(macro task) 与 微任务(micro task),最新的标准中,他们被称为
task与 jobs
宏任务有哪些:script(整体代码), setTimeout, setInterval,
setImmediate, I/O, UI rendering(渲染)微任务有哪些:process.nextTick, Promise, Object.observe(已废弃),
MutationObserver(html5新特性)
执行过程
打个比方:
小云跟小腾一起去银行办理业务,首先需要取号之后进行排队
宏任务队列
假设银行这时只有一名柜员,那小云办理业务时,小腾只能等待
单线程,宏任务次序执行
小云首先被叫到号,于是小云先办理了存款业务
执行第一个宏任务
这时柜员会询问小云是否需要办理其他业务
执行完宏任务,开始检查是否有微任务
小云告诉柜员,她还想买一份理财产品,然后办一张信用卡,最后再兑换一些牛年兑换币
执行微任务,后续宏任务被推迟
小云离开柜台后,柜员开始给小腾办理业务
执行下一个宏任务
执行栈在执行的时候,会把宏任务放在一个宏任务的任务队列,把微任务放在一个微任务的任务队列,在当前执行栈为空的时候,主线程会
查看微任务队列是否有事件存在。如果微任务队列不存在,那么会去宏任务队列中
取出一个任务
加入当前执行栈;如果微任务队列存在,则会依次执行微任务队列中的所有任务,直到微任务队列为空(同样,是吧队列中的事件加到执行栈执行),然后去宏任务队列中取出最前面的一个事件加入当前执行栈…如此反复,进入循环。
注:
宏任务和微任务的任务队列都可以有多个
当前执行栈执行完毕时会立刻先处理所有微任务队列中的事件,然后再去宏任务队列中取出一个事件。同一次事件循环中,微任务永远在宏任务之前执行。
不同的运行环境 循环策略可能有不同,这里探讨chrome、node环境
//(1)
setTimeout(()=>{
console.log(1)
},100)
//(2)
setTimeout(()=>{
console.log(2)
},100)
//(3)
new Promise(function(resolve,reject){
//(4)
console.log(3)
resolve(4)
}).then(function(val){
//(5)
console.log(val);
})
//(6)
new Promise(function(resolve,reject){
//(7)
console.log(5)
resolve(6)
}).then(function(val){
//(8)
console.log(val);
})
//(9)
console.log(7)
//(10)
setTimeout(()=>{
console.log(8)
},50)
上面的代码在node和chrome环境的正确打印顺序是 3 5 7 4 6 8 1 2
全部代码在解析后加入执行栈
执行(1),宏任务,调用webapi
setTimeout,这个方法会在100ms后将回调函数放入宏任务的任务队列执行(2),同(1),但是会比(1)稍后一点
执行(3),同步执行new Promise,然后执行(4),直接打印 3
,然后resolve(4),然后.then(),把(5)放入微任务的任务队列执行(6),同上,先打印 5
,再执行resolve(6),然后.then()里面的内容(8)加入到微任务的任务队列执行(9),同步代码,直接打印 7
执行(10),同(1)和(2),只是时间更短,会在 50ms 后将回调
console.log(8) 加入宏任务的任务队列现在执行栈清空了,开始检查微任务队列,发现(5),加入到执行栈执行,是同步代码,直接打印
4任务队列又执行完了,又检查微任务队列,发现(8),打印 6
任务队列又执行完了,检查微任务队列,没有任务,再检查宏任务队列,此时如果超过了50ms的话,会发现
console.log(8) 在宏任务队列中,于是执行 打印 8依次打印 1 2
注:因为渲染也是宏任务,需要在一次执行栈执行完后才会执行渲染,所以如果执行栈中同时有几个同步的改变同一个样式的代码,在渲染时只会渲染最后一个
new Promise(resolve => {
console.log(‘Promise’)
resolve()
console.log(‘1q1’)
})
.then(function() {
console.log(‘promise1’)
})
.then(function() {
console.log(‘promise2’)
})
console.log(‘script end’)
Promise
1q1
script end
promise1
promise2
若不想让1q1在resolve输出用 return 看
—> [#20]{.ul}
宏任务和微任务的嵌套
new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
console.log(‘setTimeout’);
}, 0);
console.log(‘promise1’);
}).then((res) => {
// 微任务
console.log(‘promise then’);
});
console.log(‘qianguyihao’);
打印结果:
promise1
qianguyihao
setTimeout
promise then
上方代码解释:在执行宏任务的过程中,创建了一个微任务。但是需要先把当前这个宏任务执行完,再去轮询异步任务的队列,进而执行微任务。
经典面试题
说出下列程序的输出顺序:
console.log(‘A’);
setTimeout(() => {
console.log(‘B’);
new Promise((resolve) => {
console.log(‘C’);
resolve();
}).then(() => {
console.log(‘D’);
});
});
new Promise((resolve) => {
console.log(‘E’);
}).then(() => {
console.log(‘F’)
});
setTimeout(() => {
console.log(‘G’);
new Promise((resolve) => {
console.log(‘H’);
}).then(() => {
console.log(‘I’);
});
})
首先执行同步任务,输出A和E
然后执行E后续的微任务,输出F
接着执行第一个宏任务中的同步任务,输出B、C
然后执行宏任务中的微任务,输出D
再之后执行第二个宏任务,输出G和H
最后执行微任务,输出I
所以完整的输出结果就是:
A
E
F
B
C
D
G
H
I
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!