7關(guān)!setTimeout+Promise+Async輸出順序?你能過幾關(guān)!
前言
大家好,我是林三心,有關(guān)于EventLoop的知識點(diǎn),在平時(shí)是考的非常多的,其實(shí)也跟我們?nèi)粘5墓ぷ鲿r(shí)息息相關(guān)的,懂得EventLoop的執(zhí)行順序,可以大大幫助我們定位出問題出在哪。其實(shí)正常的EventLoop順序是很容易分辨的,但是如果setTimeout + Promise + async/await聯(lián)起手來是非常棘手的。今天我就帶大家過五關(guān)斬六將,征服他們?。。?/p>
注明:本文不涉及Nodejs執(zhí)行機(jī)制
同步 && 異步
什么是異步,什么是同步,我不多說,我就通過小故事來講講吧。
同步:你打電話去書店訂書,老板說我查查,你不掛電話在等待,老板把查到的結(jié)果告訴你,這期間你不能做自己的事情異步:你打電話去書店訂書,老板說我查查,回頭告訴你,你把電話掛了,先去做自己的事情
JS執(zhí)行機(jī)制
其實(shí)不難,JavaScript代碼執(zhí)行機(jī)制,我就歸結(jié)為三句話
1、遇到 同步代碼直接執(zhí)行2、遇到 異步代碼先放一邊,并且將他回調(diào)函數(shù)存起來,存的地方叫事件隊(duì)列3、等所有 同步代碼都執(zhí)行完,再從事件隊(duì)列中把存起來的所有異步回調(diào)函數(shù)拿出來按順序執(zhí)行

請看以下例子
console.log(1)?//?同步
setTimeout(()?=>?{
??console.log(2)?//?異步
},?2000);
console.log(3)?//?同步
setTimeout(()?=>?{
??console.log(4)?//?異步
},?0);
console.log(5)?//?同步
輸出?:?1?3?5?4?2

宏任務(wù) && 微任務(wù)
前面說了,等所有同步代碼都執(zhí)行完,再從事件隊(duì)列里依次執(zhí)行所有異步回調(diào)函數(shù)。
其實(shí)事件隊(duì)列也是一個(gè)小團(tuán)體,人家也有自己的規(guī)則,這就類似于學(xué)校管理著許多社團(tuán),人家自己社團(tuán)內(nèi)部也有人家自己的規(guī)矩。
話說回來,為什么事件隊(duì)列里需要有自己的規(guī)則呢?要不你先想想為什么學(xué)校里的社團(tuán)里要有自己的規(guī)則要分等級,是因?yàn)橛械娜四芰?qiáng)有的人能力弱,所以也就有了等級的高低。其實(shí)事件隊(duì)列也一樣,事件隊(duì)列是用來存異步回調(diào)的,但是異步也分類型啊,異步任務(wù)分為宏任務(wù)和微任務(wù),并且微任務(wù)執(zhí)行時(shí)機(jī)先于宏任務(wù)
那宏任務(wù)和微任務(wù)都分別有哪些呢?
宏任務(wù)
| # | 瀏覽器 | Node |
|---|---|---|
| I/O | ? | ? |
| setTimeout | ? | ? |
| setInterval | ? | ? |
| setImmediate | ? | ? |
| requestAnimationFrame | ? | ? |
微任務(wù)
| # | 瀏覽器 | Node |
|---|---|---|
| Promise.prototype.then catch finally | ? | ? |
| process.nextTick | ? | ? |
| MutationObserver | ? | ? |
執(zhí)行流程
那就來說說整體的執(zhí)行的流程吧

例子
大家可以根據(jù)我的解題步驟去走,基本90%的題目都是沒什么壓力的?。?!
1、標(biāo)記區(qū)分異步和同步 2、異步中,標(biāo)記區(qū)分宏任務(wù)和微任務(wù) 3、分輪數(shù),一輪一輪慢慢走
console.log(1)?//?同步
setTimeout(()?=>?{
??console.log(2)?//?異步:宏任務(wù)
});
console.log(3)?//?同步
Promise.resolve().then(()=>{?//?異步:微任務(wù)
??console.log(4)?
})
console.log(5)?//?同步
第一輪
說明:先把同步的執(zhí)行輸出 輸出:1,3,5 產(chǎn)生宏任務(wù): setTimeout,產(chǎn)生微任務(wù):Promise.prototype.then
第二輪
說明:微任務(wù)先執(zhí)行 輸出:4 產(chǎn)生宏任務(wù):無,產(chǎn)生微任務(wù):無 剩余宏任務(wù): setTimeout,剩余微任務(wù):無
第三輪(結(jié)束)
說明:執(zhí)行宏任務(wù) 輸出:2 產(chǎn)生宏任務(wù):無,產(chǎn)生微任務(wù):無 剩余宏任務(wù):無,剩余微任務(wù):無
第一關(guān)
想一想我剛剛說的解題思路,大家可以按照那個(gè)思路來,這道題也就是分分鐘的事情啦
console.log(1)
setTimeout(()?=>?{
??console.log(2)
??Promise.resolve().then(()?=>?{
????console.log(3)
??})
});
console.log(4)
new?Promise((resolve,reject)?=>?{
??console.log(5)
}).then(()?=>?{
??console.log(6)
??setTimeout(()?=>?{
????console.log(7)
??})
})
console.log(8)
第一步:標(biāo)記
注意:Promise的
executor是同步的哦?。。?/p>
console.log(1)?//?同步
setTimeout(()?=>?{
??console.log(2)?//?異步:宏任務(wù) setTimeout1
??Promise.resolve().then(()?=>?{?//?異步:微任務(wù) then1
????console.log(3)
??})
});
console.log(4)?//?同步
new?Promise((resolve,reject)?=>?{
??console.log(5)?//?同步
??resolve()
}).then(()?=>?{?//?異步:微任務(wù) then2
??console.log(6)
??setTimeout(()?=>?{
????console.log(7)?//?異步:宏任務(wù) setTimeout2
??})
})
console.log(8)?//?同步
第二步:分輪
| 輪數(shù) | 說明 | 輸出 | 產(chǎn)生 | 剩余 |
|---|---|---|---|---|
| 第一輪 | 執(zhí)行外層同步輸出 | 1,4,5,8 | 宏任務(wù):setTimeout1微任務(wù): then2 | 宏任務(wù):setTimeout1微任務(wù): then2 |
| 第二輪 | 執(zhí)行微任務(wù)then2 | 6 | 宏任務(wù):setTimeout2微任務(wù):無 | 宏任務(wù):setTimeout1,setTimeout2微任務(wù):無 |
| 第三輪 | 執(zhí)行宏任務(wù)setTimeout1 | 2 | 宏任務(wù):無 微任務(wù): then1 | 宏任務(wù):setTimeout2微任務(wù): then1 |
| 第四輪 | 執(zhí)行微任務(wù)then1 | 3 | 宏任務(wù):無 微任務(wù):無 | 宏任務(wù):setTimeout2微任務(wù):無 |
| 第五輪 | 執(zhí)行宏任務(wù)setTimeout2 | 7 | 宏任務(wù):無 微任務(wù):無 | 宏任務(wù):無 微任務(wù):無 |
第二關(guān)
大家在遇到Promise.then.then這種時(shí),如果有點(diǎn)懵逼的同學(xué),可以轉(zhuǎn)換一下,下面會說到
注意:
then方法會自動返回一個(gè)新的Promise,也就是return new Promise,具體的Promise源碼,大家可以看我這篇看了就會,手寫Promise原理,最通俗易懂的版本【閱讀:1.1w,點(diǎn)贊:430】
setTimeout(()?=>?{
??console.log(1)
},?0)
console.log(2)
const?p?=?new?Promise((resolve)?=>?{
??console.log(3)
??resolve()
}).then(()?=>?{
??console.log(4)
}).then(()?=>?{
??console.log(5)
})
console.log(6)
第一步:標(biāo)記 + 轉(zhuǎn)換
注意:這里的轉(zhuǎn)換,只針對做題時(shí),比較好理解,平時(shí)不要這么轉(zhuǎn)換,平時(shí)這么轉(zhuǎn)換是不太合適的,是錯的
setTimeout(()?=>?{?//?異步:宏任務(wù) setTimeout
??console.log(1)
},?0)
console.log(2)?//?同步
const?p?=?new?Promise((resolve)?=>?{?//?p?是?then1?執(zhí)行返回的新?Promise
??console.log(3)?//?同步
??resolve()
}).then(()?=>?{?//?異步:微任務(wù) then1
??console.log(4)
??//?拿著?p?重新?then
??p.then(()?=>?{?//?異步:微任務(wù) then2
????console.log(5)
??})
})
console.log(6)?//?同步?6
第二步:分輪
| 輪數(shù) | 說明 | 輸出 | 產(chǎn)生 | 剩余 |
|---|---|---|---|---|
| 第一輪 | 執(zhí)行同步輸出 | 2,3,6 | 宏任務(wù):setTimeout微任務(wù): then1 | 宏任務(wù):setTimeout微任務(wù): then1 |
| 第二輪 | 執(zhí)行微任務(wù)then1 | 4 | 宏任務(wù):無 微任務(wù): then2 | 宏任務(wù):setTimeout微任務(wù): then2 |
| 第三輪 | 執(zhí)行微任務(wù)then2 | 5 | 宏任務(wù):無 微任務(wù):無 | 宏任務(wù):setTimeout微任務(wù):無 |
| 第四輪 | 執(zhí)行宏任務(wù)setTimeout | 1 | 宏任務(wù):無 微任務(wù):無 | 宏任務(wù):無 微任務(wù):無 |
第三關(guān)
再說一遍:大家在遇到Promise.then.then這種時(shí),如果有點(diǎn)懵逼的同學(xué),可以轉(zhuǎn)換一下
注意:
then方法會自動返回一個(gè)新的Promise,也就是return new Promise,具體的Promise源碼,大家可以看我這篇看了就會,手寫Promise原理,最通俗易懂的版本【閱讀:1.1w,點(diǎn)贊:430】
new?Promise((resolve,reject)=>{
??console.log(1)
??resolve()
}).then(()=>{
??console.log(2)
??new?Promise((resolve,reject)=>{
??????console.log(3)
??????resolve()
??}).then(()=>{
??????console.log(4)
??}).then(()=>{
??????console.log(5)
??})
}).then(()=>{
??console.log(6)
})
第一步:標(biāo)記 + 轉(zhuǎn)換
注意:這里的轉(zhuǎn)換,只針對做題時(shí),比較好理解,平時(shí)不要這么轉(zhuǎn)換,平時(shí)這么轉(zhuǎn)換是不太合適的,是錯的
const?p1?=?new?Promise((resolve,?reject)?=>?{?//?p1?是?then1?執(zhí)行返回的新?Promise
??console.log(1)?//?同步
??resolve()
}).then(()?=>?{?//?異步:微任務(wù) then1
??console.log(2)
??const?p2?=?new?Promise((resolve,?reject)?=>?{?//?p2?是?then2?執(zhí)行返回的新?Promise
????console.log(3)?//?then1?里的?同步
????resolve()
??}).then(()?=>?{?//?異步:微任務(wù) then2
????console.log(4)
????
????//?拿著?p2?重新?then
????p2.then(()?=>?{?//?異步:微任務(wù) then3
??????console.log(5)
????})
??})
??
??//?拿著?p1?重新?then
??p1.then(()?=>?{?//?異步:微任務(wù) then4
????console.log(6)
??})
})
第二步:分輪
| 輪數(shù) | 說明 | 輸出 | 產(chǎn)生 | 剩余 |
|---|---|---|---|---|
| 第一輪 | 執(zhí)行外層同步輸出 | 1 | 宏任務(wù):無 微任務(wù): then1 | 宏任務(wù):無 微任務(wù): then1 |
| 第二輪 | 執(zhí)行微任務(wù)then1 | 2,3 | 宏任務(wù):無 微任務(wù): then2、then4 | 宏任務(wù):無 微任務(wù): then2、then4 |
| 第三輪 | 執(zhí)行微任務(wù)then2,then4 | 4,6 | 宏任務(wù):無 微任務(wù): then3 | 宏任務(wù):無 微任務(wù): then3 |
| 第四輪 | 執(zhí)行微任務(wù)then3 | 5 | 宏任務(wù):無 微任務(wù):無 | 宏任務(wù):無 微任務(wù):無 |
第四關(guān)
這一關(guān),比上一關(guān)多了一個(gè)return
前面說了,then方法會自動返回一個(gè)新的Promise,相當(dāng)于return new Promise,但是如果你手動寫了return Promise,那return的就是你手動寫的這個(gè)Promise
new?Promise((resolve,?reject)?=>?{
??console.log(1)
??resolve()
}).then(()?=>?{
??console.log(2)
??//?多了個(gè)return
??return?new?Promise((resolve,?reject)?=>?{
????console.log(3)
????resolve()
??}).then(()?=>?{
????console.log(4)
??}).then(()?=>?{?//?相當(dāng)于return了這個(gè)then的執(zhí)行返回Promise
????console.log(5)
??})
}).then(()?=>?{
??console.log(6)
})
第一步:標(biāo)記 + 轉(zhuǎn)換
由于return的是then3執(zhí)行返回的Promise,所以then4其實(shí)是then3Promise.then(),所以可轉(zhuǎn)換為then3.then4
new?Promise((resolve,?reject)?=>?{
??console.log(1)?//?同步
??resolve()
}).then(()?=>?{?//?異步:微任務(wù) then1
??console.log(2)?//?then1?中的?同步
??new?Promise((resolve,?reject)?=>?{
????console.log(3)?//?then1?中的?同步
????resolve()
??}).then(()?=>?{?//?異步:微任務(wù) then2
????console.log(4)
??}).then(()?=>?{?//?異步:微任務(wù) then3
????console.log(5)
??}).then(()?=>?{?//?異步:微任務(wù) then4
????console.log(6)
??})
})
第二步:分輪
| 輪數(shù) | 說明 | 輸出 | 產(chǎn)生 | 剩余 |
|---|---|---|---|---|
| 第一輪 | 執(zhí)行外層同步輸出 | 1 | 宏任務(wù):無 微任務(wù): then1 | 宏任務(wù):無 微任務(wù): then1 |
| 第二輪 | 執(zhí)行微任務(wù)then1 | 2,3 | 宏任務(wù):無 微任務(wù): then2、then3、then4 | 宏任務(wù):無 微任務(wù): then2、then3、then4 |
| 第三輪 | 執(zhí)行微任務(wù)then2、then3、then4 | 4,5,6 | 宏任務(wù):無 微任務(wù):無 | 宏任務(wù):無 微任務(wù):無 |
第五關(guān)
new?Promise((resolve,?reject)?=>?{
??console.log(1)
??resolve()
}).then(()?=>?{
??console.log(2)
??new?Promise((resolve,?reject)?=>?{
????console.log(3)
????resolve()
??}).then(()?=>?{
????console.log(4)
??}).then(()?=>?{
????console.log(5)
??})
}).then(()?=>?{
??console.log(6)
})
new?Promise((resolve,?reject)?=>?{
??console.log(7)
??resolve()
}).then(()?=>?{
??console.log(8)
})
第一步:標(biāo)記 + 轉(zhuǎn)換
const?p1?=?new?Promise((resolve,?reject)?=>?{?//?p1?是?then1?執(zhí)行返回的新?Promise
??console.log(1)?//?同步
??resolve()
}).then(()?=>?{?//?異步:微任務(wù) then1
??console.log(2)
??const?p2?=?new?Promise((resolve,?reject)?=>?{?//?p2?是?then2?執(zhí)行返回的新?Promise
????console.log(3)?//?then1?里的?同步
????resolve()
??}).then(()?=>?{?//?異步:微任務(wù) then2
????console.log(4)
????
????//?拿著?p2?重新?then
????p2.then(()?=>?{?//?異步:微任務(wù) then3
??????console.log(5)
????})
??})
??
??//?拿著?p1?重新?then
??p1.then(()?=>?{?//?異步:微任務(wù) then4
????console.log(6)
??})
})
new?Promise((resolve,?reject)?=>?{
??console.log(7)?//?同步
??resolve()
}).then(()?=>?{??//?異步:微任務(wù) then5
??console.log(8)
})
第二步:分輪
| 輪數(shù) | 說明 | 輸出 | 產(chǎn)生 | 剩余 |
|---|---|---|---|---|
| 第一輪 | 執(zhí)行外層同步輸出 | 1,7 | 宏任務(wù):無 微任務(wù): then1、then5 | 宏任務(wù):無 微任務(wù): then1、then5 |
| 第二輪 | 執(zhí)行微任務(wù)then1、then5 | 2,3,8 | 宏任務(wù):無 微任務(wù): then2、then4 | 宏任務(wù):無 微任務(wù): then2、then4 |
| 第三輪 | 執(zhí)行微任務(wù)then2、then4 | 4,6 | 宏任務(wù):無 微任務(wù): then5 | 宏任務(wù):無 微任務(wù): then5 |
| 第四輪 | 執(zhí)行微任務(wù)then5 | 5 | 宏任務(wù):無 微任務(wù):無 | 宏任務(wù):無 微任務(wù):無 |
第六關(guān)
其實(shí)async/await的內(nèi)部實(shí)現(xiàn)原理,是依賴于Promise.prototype.then的不斷嵌套,它在題中也是可以轉(zhuǎn)換的,下面會講到。
有興趣的朋友可以看我這篇7張圖,20分鐘就能搞定的async/await原理!為什么要拖那么久【閱讀量:1.8w,點(diǎn)贊:571】
async?function?async1()?{
??console.log(1);
??await?async2();
??console.log(2);
}
async?function?async2()?{
??console.log(3);
}
console.log(4);
setTimeout(function?()?{
??console.log(5);
});
async1()
new?Promise(function?(resolve,?reject)?{
??console.log(6);
??resolve();
}).then(function?()?{
??console.log(7);
});
console.log(8);
第一步:標(biāo)記 + 轉(zhuǎn)換
注意:這里的轉(zhuǎn)換,只針對做題時(shí),比較好理解,平時(shí)不要這么轉(zhuǎn)換,平時(shí)這么轉(zhuǎn)換是不太合適的
console.log(4);?//?同步
setTimeout(function?()?{
??console.log(5);?//?異步:宏任務(wù) setTimeout
});
//?async1函數(shù)可轉(zhuǎn)換成
console.log(1)?//?同步
new?Promise((resolve,?reject)?=>?{
??console.log(3)?//?同步
??resolve()
}).then(()?=>?{?//?異步:微任務(wù) then1
??console.log(2)
})
//?async1函數(shù)結(jié)束
new?Promise(function?(resolve,?reject)?{
??console.log(6);?//?同步
??resolve();
}).then(function?()?{?//?異步:微任務(wù) then2
??console.log(7);
});
console.log(8);?//?同步
第二步:分輪
| 輪數(shù) | 說明 | 輸出 | 產(chǎn)生 | 剩余 |
|---|---|---|---|---|
| 第一輪 | 執(zhí)行同步輸出 | 4,1,3,6,8 | 宏任務(wù):setTimeout微任務(wù): then1、then2 | 宏任務(wù):setTimeout微任務(wù): then1、then2 |
| 第二輪 | 執(zhí)行微任務(wù)then1、then2 | 2,7 | 宏任務(wù):無 微任務(wù):無 | 宏任務(wù):setTimeout微任務(wù):無 |
| 第三輪 | 執(zhí)行宏任務(wù)setTimeout | 5 | 宏任務(wù):無 微任務(wù): then5 | 宏任務(wù):無 微任務(wù):無 |
課后作業(yè)
最后給大家布置兩道作業(yè),幫大家鞏固一下本文章所學(xué)的知識,大家也可以加入我的摸魚群,進(jìn)行答案的討論。進(jìn)群點(diǎn)擊這里進(jìn)群,目前已有將近1000人加入學(xué)習(xí),我會定時(shí)舉辦學(xué)習(xí)分享,模擬面試等學(xué)習(xí)活動,一起學(xué)習(xí),共同進(jìn)步?。?!
第一題(思考題)
想一想下面這兩個(gè)有什么區(qū)別?
//?第一種
const?p?=?new?Promise((resolve,?reject)?=>?{
??resolve()
}).then(()?=>?console.log(1)).then(()?=>?console.log(2))
//?第二種
const?p?=?new?Promise((resolve,?reject)?=>?{
??resolve()
})
p.then(()?=>?console.log(1))
p.then(()?=>?console.log(2))
第二題(問題不大)
async?function?async1()?{
??console.log(1);
??await?async2();
??console.log(2);
}
async?function?async2()?{
??console.log(3);
}
new?Promise((resolve,?reject)?=>?{
??setTimeout(()?=>?{
????resolve()
????console.log(4)
??},?1000);
}).then(()?=>?{
??console.log(5)
??new?Promise((resolve,?reject)?=>?{
????setTimeout(()?=>?{
??????async1()
??????resolve()
??????console.log(6)
????},?1000)
??}).then(()?=>?{
????console.log(7)
??}).then(()?=>?{
????console.log(8)
??})
}).then(()?=>?{
??console.log(9)
})
new?Promise((resolve,?reject)?=>?{
??console.log(10)
??setTimeout(()?=>?{
????resolve()
????console.log(11)
??},?3000);
}).then(()?=>?{
??console.log(12)
})
第三題(有點(diǎn)難度)
這道題能一分鐘內(nèi)做出來的找我領(lǐng)獎,這道題需要具備一定的Promise原理基礎(chǔ) + async/await原理基礎(chǔ)才能比較輕松的答對,有興趣的同學(xué)可以看我之前寫過的文章
看了就會,手寫Promise原理,最通俗易懂的版本【閱讀:1.1w,點(diǎn)贊:430】 7張圖,20分鐘就能搞定的async/await原理!為什么要拖那么久【閱讀量:1.8w,點(diǎn)贊:571】
async?function?async1()?{
??console.log('async1?start')
??await?async2()
??console.log('async1?end')
}
async?function?async2()?{
??console.log('async?start')
??return?new?Promise((resolve,?reject)?=>?{
????resolve()
????console.log('async2?promise')
??})
}
console.log('script?start')
setTimeout(()?=>?{
??console.log('setTimeout')
},?0);
async1()
new?Promise((resolve)?=>?{
??console.log('promise1')
??resolve()
}).then(()?=>?{
??console.log('promise2')
}).then(()?=>?{
??console.log('promise3')
})
console.log('script?end')
結(jié)語
加入我的摸魚群 ,我會定時(shí)直播模擬面試,答疑解惑,先已有將近1000人加入學(xué)習(xí)

