問題來源
在學(xué)習(xí)Promise時在stackoverflow上看到一個解釋Promise運行順序回答灰嫉。
之前在學(xué)習(xí)異步編程中講解了MacroTask和MicroTask, 但在最近深入EventLoop后又有了更多的了解
EventLoop谭企、MacroTask、MicroTask之間的關(guān)系
- macrotasks 與 microtasks 各自的 API
macrotasks: setTimeout, setInterval, setImmediate, requestAnimationFrame, I/O, UI rendering
microtasks: process.nextTick, Promises, Object.observe, MutationObserver
-
一張圖先了解microtasks 與macrotasks 在eventloop隊列里的位置
這里用了上一章EventLoop 事件循環(huán)文章里的圖,并在回調(diào)隊列里標(biāo)注里microtask的位置焚志。
microtasks 與macrotasks 在eventloop 里的流程
在沒有引入microtasks 概念前事件循環(huán)是這樣執(zhí)行的
while (queue是否有task) {
執(zhí)行task
}
引入microtasks 概念后
while (queue是否有macrotasks) {
if (microtasks) 執(zhí)行空microtasks
再執(zhí)行macrotasks
}
microtasks 與macrotasks 在eventloop 里實際執(zhí)行結(jié)果
// 例1
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
console.log('script end');
// 結(jié)果
script start
script end
promise1
promise2
setTimeout
// 當(dāng)前循環(huán)結(jié)束
// 進入下一個循環(huán)
從webapi在Eventloop的執(zhí)行環(huán)境我們可以知道setTimeout在當(dāng)前事件循環(huán)中將會在script end后執(zhí)行映凳,這是沒問題的胆筒。
而promise作為microtasks將會在當(dāng)前事件循環(huán)內(nèi)的macrotasks之前執(zhí)行完畢。
setTimeout作為macrotasks在例1中是最后執(zhí)行的诈豌。
例2
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);
// 執(zhí)行順序
3 4 6 8 7 5 2 1
例2
process.nextTick在node環(huán)境中仆救,屬于microtask
setImmediate在macrotasks,優(yōu)先級小于setTimeout
定義new Promise() 是同步代碼矫渔,在棧內(nèi)先執(zhí)行
例3
const p = new Promise((res, rej) => {
res(1)
console.log('定義new Promise - 同步')
}).then(val => {
console.log('microtask start')
console.log('執(zhí)行then彤蔽,enqueue micarotask 1')
console.log(val) // 1
})
Promise.resolve({
then(res, rej) {
console.log('執(zhí)行then,enqueue micarotask 2')
res(5)
}
}).then(val => {
console.log('執(zhí)行then庙洼,enqueue micarotask 3')
console.log(val) // 5
})
console.log('逐行執(zhí)行1 - 同步')
console.log('逐行執(zhí)行2 - 同步')
console.log(3) // 3
setTimeout(console.log, 0, 'macrotask start') // 4
setTimeout(console.log, 0, 4) // 4
定義new Promise - 同步
逐行執(zhí)行1 - 同步
逐行執(zhí)行2 - 同步
3
// 同步隊列執(zhí)行完畢為空 進入下一個棧
microtask start
執(zhí)行then顿痪,enqueue micarotask 1
1
執(zhí)行then,enqueue micarotask 2
執(zhí)行then油够,enqueue micarotask 3
5
// microtask執(zhí)行完畢為空 進入下一個棧
macrotask start
4
// macrotask執(zhí)行完畢為空 結(jié)束
例3
定義new Promise是同步函數(shù)
Promise.resolve等api為異步micarotask
例4
<div class="outer">
<div class="inner"></div>
</div>
var outer = document.querySelector('.outer');
var inner = document.querySelector('.inner');
new MutationObserver(function() {
console.log('mutate');
}).observe(outer, {
attributes: true
});
function onClick() {
console.log('click');
setTimeout(function() {
console.log('timeout');
}, 0);
Promise.resolve().then(function() {
console.log('promise');
});
outer.setAttribute('data-random', Math.random());
}
inner.addEventListener('click', onClick);
outer.addEventListener('click', onClick);
// 同時點擊到兩個div時執(zhí)行結(jié)果
click
promise
mutate
click
promise
mutate
timeout
timeout
例4執(zhí)行效果
沒點擊前:
1綁定new MutationObserver 存入瀏覽器資源
2綁定兩個div元素的click事件 存入瀏覽器資源
3觸發(fā)outer元素click的onClick 存入瀏覽器資源
4觸發(fā)inner元素click的onClick 存入瀏覽器資源
5先執(zhí)行outer的回調(diào)
6輸出click
7執(zhí)行setTimeout - macrotask存入瀏覽器資源
8執(zhí)行outer.setAttribute('data-random', Math.random())蚁袭,觸發(fā)MutationObserver - marcotask 等待microtask先執(zhí)行
9執(zhí)行Promise.resolve - microtask 輸出promise
10microtask 執(zhí)行完畢,執(zhí)行MutationObserver輸出mutate
-----下面執(zhí)行的并不是outer回調(diào)里的setTimeout------
11執(zhí)行inner的回調(diào)
12輸出inner回調(diào)的click
13執(zhí)行inner回調(diào)的setTimeout - macrotask存入瀏覽器資源
14執(zhí)行inner回調(diào)outer.setAttribute('data-random', Math.random())石咬,觸發(fā)MutationObserver - marcotask 等待microtask先執(zhí)行
15執(zhí)行inner回調(diào)的Promise.resolve - microtask 輸出promise
16microtask 執(zhí)行完畢揩悄,執(zhí)行MutationObserver輸出mutate
--最后因為兩個setTimeout都是在觸發(fā)inner回調(diào)后存入瀏覽器資源的--
所以最后兩個setTimeout回調(diào)完成排入隊列執(zhí)行.