你不知道的 async、await 魔鬼細(xì)節(jié)
共 19811字,需瀏覽 40分鐘
·
2024-08-09 08:45
關(guān)于promise、async/await的使用相信很多小伙伴都比較熟悉了,但是提到事件循環(huán)機(jī)制輸出結(jié)果類似的題目,你敢說(shuō)都會(huì)?
試一試?
??1:
async function async1 () {
await new Promise((resolve, reject) => {
resolve()
})
console.log('A')
}
async1()
new Promise((resolve) => {
console.log('B')
resolve()
}).then(() => {
console.log('C')
}).then(() => {
console.log('D')
})
// 最終結(jié)果??: B A C D
??2:
async function async1 () {
await async2()
console.log('A')
}
async function async2 () {
return new Promise((resolve, reject) => {
resolve()
})
}
async1()
new Promise((resolve) => {
console.log('B')
resolve()
}).then(() => {
console.log('C')
}).then(() => {
console.log('D')
})
// 最終結(jié)果??: B C D A
?基本一樣的代碼為什么會(huì)出現(xiàn)差別,話不多說(shuō)??
1、async 函數(shù)返回值
在討論 await 之前,先聊一下 async 函數(shù)處理返回值的問(wèn)題,它會(huì)像 Promise.prototype.then 一樣,會(huì)對(duì)返回值的類型進(jìn)行辨識(shí)。
??根據(jù)返回值的類型,引起 js引擎 對(duì)返回值處理方式的不同
??結(jié)論:
async函數(shù)在拋出返回值時(shí),會(huì)根據(jù)返回值類型開(kāi)啟不同數(shù)目的微任務(wù)
return結(jié)果值:非 thenable、非promise(不等待)return結(jié)果值: thenable(等待 1個(gè)then的時(shí)間)return結(jié)果值: promise(等待 2個(gè)then的時(shí)間)
??1:
async function testA () {
return 1;
}
testA().then(() => console.log(1));
Promise.resolve()
.then(() => console.log(2))
.then(() => console.log(3));
// (不等待)最終結(jié)果??: 1 2 3
??2:
async function testB () {
return {
then (cb) {
cb();
}
};
}
testB().then(() => console.log(1));
Promise.resolve()
.then(() => console.log(2))
.then(() => console.log(3));
// (等待一個(gè)then)最終結(jié)果??: 2 1 3
??3:
async function testC () {
return new Promise((resolve, reject) => {
resolve()
})
}
testC().then(() => console.log(1));
Promise.resolve()
.then(() => console.log(2))
.then(() => console.log(3));
// (等待兩個(gè)then)最終結(jié)果??: 2 3 1
async function testC () {
return new Promise((resolve, reject) => {
resolve()
})
}
testC().then(() => console.log(1));
Promise.resolve()
.then(() => console.log(2))
.then(() => console.log(3))
.then(() => console.log(4))
// (等待兩個(gè)then)最終結(jié)果??: 2 3 1 4
看了這三個(gè)??是不是對(duì)上面的結(jié)論有了更深的認(rèn)識(shí)?
稍安勿躁,來(lái)試試一個(gè)經(jīng)典面試題??
async function async1 () {
console.log('1')
await async2()
console.log('AAA')
}
async function async2 () {
console.log('3')
return new Promise((resolve, reject) => {
resolve()
console.log('4')
})
}
console.log('5')
setTimeout(() => {
console.log('6')
}, 0);
async1()
new Promise((resolve) => {
console.log('7')
resolve()
}).then(() => {
console.log('8')
}).then(() => {
console.log('9')
}).then(() => {
console.log('10')
})
console.log('11')
// 最終結(jié)果??: 5 1 3 4 7 11 8 9 AAA 10 6
??做錯(cuò)了吧?
哈哈沒(méi)關(guān)系
步驟拆分??:
先執(zhí)行同步代碼,輸出
5執(zhí)行
setTimeout,是放入宏任務(wù)異步隊(duì)列中接著執(zhí)行
async1函數(shù),輸出1執(zhí)行
async2函數(shù),輸出3
Promise構(gòu)造器中代碼屬于同步代碼,輸出4
async2函數(shù)的返回值是Promise,等待2個(gè)then后放行,所以AAA暫時(shí)無(wú)法輸出
async1函數(shù)暫時(shí)結(jié)束,繼續(xù)往下走,輸出7同步代碼,輸出
11執(zhí)行第一個(gè)
then,輸出8執(zhí)行第二個(gè)
then,輸出9終于等到了兩個(gè)
then執(zhí)行完畢,執(zhí)行async1函數(shù)里面剩下的,輸出AAA再執(zhí)行最后一個(gè)微任務(wù)
then,輸出10執(zhí)行最后的宏任務(wù)
setTimeout,輸出6
?是不是豁然開(kāi)朗,歡迎點(diǎn)贊收藏!
2、await 右值類型區(qū)別
2.1、非 thenable
??1:
async function test () {
console.log(1);
await 1;
console.log(2);
}
test();
console.log(3);
// 最終結(jié)果??: 1 3 2
??2:
function func () {
console.log(2);
}
async function test () {
console.log(1);
await func();
console.log(3);
}
test();
console.log(4);
// 最終結(jié)果??: 1 2 4 3
??3:
async function test () {
console.log(1);
await 123
console.log(2);
}
test();
console.log(3);
Promise.resolve()
.then(() => console.log(4))
.then(() => console.log(5))
.then(() => console.log(6))
.then(() => console.log(7));
// 最終結(jié)果??: 1 3 2 4 5 6 7
Note:
await后面接非thenable類型,會(huì)立即向微任務(wù)隊(duì)列添加一個(gè)微任務(wù)then,但不需等待
2.2、thenable類型
async function test () {
console.log(1);
await {
then (cb) {
cb();
},
};
console.log(2);
}
test();
console.log(3);
Promise.resolve()
.then(() => console.log(4))
.then(() => console.log(5))
.then(() => console.log(6))
.then(() => console.log(7));
// 最終結(jié)果??: 1 3 4 2 5 6 7
Note:
await后面接thenable類型,需要等待一個(gè)then的時(shí)間之后執(zhí)行
2.3、Promise類型
async function test () {
console.log(1);
await new Promise((resolve, reject) => {
resolve()
})
console.log(2);
}
test();
console.log(3);
Promise.resolve()
.then(() => console.log(4))
.then(() => console.log(5))
.then(() => console.log(6))
.then(() => console.log(7));
// 最終結(jié)果??: 1 3 2 4 5 6 7
?為什么表現(xiàn)的和非 thenable 值一樣呢?為什么不等待兩個(gè) then 的時(shí)間呢?
Note:
TC 39(ECMAScript標(biāo)準(zhǔn)制定者) 對(duì) await后面是promise的情況如何處理進(jìn)行了一次修改,移除了額外的兩個(gè)微任務(wù),在早期版本,依然會(huì)等待兩個(gè)then的時(shí)間有大佬翻譯了官方解釋:更快的 async 函數(shù)和 promises[1],但在這次更新中并沒(méi)有修改 thenable的情況
這樣做可以極大的優(yōu)化 await 等待的速度??
async function func () {
console.log(1);
await 1;
console.log(2);
await 2;
console.log(3);
await 3;
console.log(4);
}
async function test () {
console.log(5);
await func();
console.log(6);
}
test();
console.log(7);
Promise.resolve()
.then(() => console.log(8))
.then(() => console.log(9))
.then(() => console.log(10))
.then(() => console.log(11));
// 最終結(jié)果??: 5 1 7 2 8 3 9 4 10 6 11
Note:
await和Promise.prototype.then雖然很多時(shí)候可以在時(shí)間順序上能等效,但是它們之間有本質(zhì)的區(qū)別。
test函數(shù)中的await會(huì)等待func函數(shù)中所有的await取得 恢復(fù)函數(shù)執(zhí)行 的命令并且整個(gè)函數(shù)執(zhí)行完畢后才能獲得取得 恢復(fù)函數(shù)執(zhí)行的命令;也就是說(shuō), func函數(shù)的await此時(shí)不能在時(shí)間的順序上等效then,而要等待到test函數(shù)完全執(zhí)行完畢;比如這里的數(shù)字 6很晚才輸出,如果單純看成then的話,在下一個(gè)微任務(wù)隊(duì)列執(zhí)行時(shí)6就應(yīng)該作為同步代碼輸出了才對(duì)。
所以我們可以合并兩個(gè)函數(shù)的代碼??
async function test () {
console.log(5);
console.log(1);
await 1;
console.log(2);
await 2;
console.log(3);
await 3;
console.log(4);
await null;
console.log(6);
}
test();
console.log(7);
Promise.resolve()
.then(() => console.log(8))
.then(() => console.log(9))
.then(() => console.log(10))
.then(() => console.log(11));
// 最終結(jié)果??: 5 1 7 2 8 3 9 4 10 6 11
因?yàn)閷⒃镜暮瘮?shù)融合,此時(shí)的 await 可以等效為 Promise.prototype.then,又完全可以等效如下代碼??
async function test () {
console.log(5);
console.log(1);
Promise.resolve()
.then(() => console.log(2))
.then(() => console.log(3))
.then(() => console.log(4))
.then(() => console.log(6))
}
test();
console.log(7);
Promise.resolve()
.then(() => console.log(8))
.then(() => console.log(9))
.then(() => console.log(10))
.then(() => console.log(11));
// 最終結(jié)果??: 5 1 7 2 8 3 9 4 10 6 11
以上三種寫法在時(shí)間的順序上完全等效,所以你 完全可以將 await 后面的代碼可以看做在 then 里面執(zhí)行的結(jié)果,又因?yàn)?async 函數(shù)會(huì)返回 promise 實(shí)例,所以還可以等效成??
async function test () {
console.log(5);
console.log(1);
}
test()
.then(() => console.log(2))
.then(() => console.log(3))
.then(() => console.log(4))
.then(() => console.log(6))
console.log(7);
Promise.resolve()
.then(() => console.log(8))
.then(() => console.log(9))
.then(() => console.log(10))
.then(() => console.log(11));
// 最終結(jié)果??: 5 1 7 2 8 3 9 4 10 6 11
可以發(fā)現(xiàn),test 函數(shù)全是走的同步代碼...
所以??:**async/await 是用同步的方式,執(zhí)行異步操作**
3、??
??1:
async function async2 () {
new Promise((resolve, reject) => {
resolve()
})
}
async function async3 () {
return new Promise((resolve, reject) => {
resolve()
})
}
async function async1 () {
// 方式一:最終結(jié)果:B A C D
// await new Promise((resolve, reject) => {
// resolve()
// })
// 方式二:最終結(jié)果:B A C D
// await async2()
// 方式三:最終結(jié)果:B C D A
await async3()
console.log('A')
}
async1()
new Promise((resolve) => {
console.log('B')
resolve()
}).then(() => {
console.log('C')
}).then(() => {
console.log('D')
})
大致思路??:
首先,** async函數(shù)的整體返回值永遠(yuǎn)都是Promise,無(wú)論值本身是什么**方式一: await的是Promise,無(wú)需等待方式二: await的是async函數(shù),但是該函數(shù)的返回值本身是**非thenable**,無(wú)需等待方式三: await的是async函數(shù),且返回值本身是Promise,需等待兩個(gè)then時(shí)間
??2:
function func () {
console.log(2);
// 方式一:1 2 4 5 3 6 7
// Promise.resolve()
// .then(() => console.log(5))
// .then(() => console.log(6))
// .then(() => console.log(7))
// 方式二:1 2 4 5 6 7 3
return Promise.resolve()
.then(() => console.log(5))
.then(() => console.log(6))
.then(() => console.log(7))
}
async function test () {
console.log(1);
await func();
console.log(3);
}
test();
console.log(4);
步驟拆分??:
方式一:
同步代碼輸出 1、2,接著將log(5)處的then1加入微任務(wù)隊(duì)列,await拿到確切的func函數(shù)返回值undefined,將后續(xù)代碼放入微任務(wù)隊(duì)列(then2,可以這樣理解)執(zhí)行同步代碼輸出 4,到此,所有同步代碼完畢執(zhí)行第一個(gè)放入的微任務(wù) then1輸出5,產(chǎn)生log(6)的微任務(wù)then3執(zhí)行第二個(gè)放入的微任務(wù) then2輸出3然后執(zhí)行微任務(wù) then3,輸出6,產(chǎn)生log(7)的微任務(wù)then4執(zhí)行 then4,輸出7方式二:
同步代碼輸出 1、2,await拿到func函數(shù)返回值,但是并未獲得具體的結(jié)果(由Promise本身機(jī)制決定),暫停執(zhí)行當(dāng)前async函數(shù)內(nèi)的代碼(跳出、讓行)輸出 4,到此,所有同步代碼完畢await一直等到Promise.resolve().then...執(zhí)行完成,再放行輸出3
方式二沒(méi)太明白?
繼續(xù)??
function func () {
console.log(2);
return Promise.resolve()
.then(() => console.log(5))
.then(() => console.log(6))
.then(() => console.log(7))
}
async function test () {
console.log(1);
await func()
console.log(3);
}
test();
console.log(4);
new Promise((resolve) => {
console.log('B')
resolve()
}).then(() => {
console.log('C')
}).then(() => {
console.log('D')
})
// 最終結(jié)果??: 1 2 4 B 5 C 6 D 7 3
還是沒(méi)懂?
繼續(xù)??
async function test () {
console.log(1);
await Promise.resolve()
.then(() => console.log(5))
.then(() => console.log(6))
.then(() => console.log(7))
console.log(3);
}
test();
console.log(4);
new Promise((resolve) => {
console.log('B')
resolve()
}).then(() => {
console.log('C')
}).then(() => {
console.log('D')
})
// 最終結(jié)果??: 1 4 B 5 C 6 D 7 3
Note:
綜上,
await一定要等到右側(cè)的表達(dá)式有確切的值才會(huì)放行,否則將一直等待(阻塞當(dāng)前async函數(shù)內(nèi)的后續(xù)代碼),不服看看這個(gè)??
function func () {
return new Promise((resolve) => {
console.log('B')
// resolve() 故意一直保持pending
})
}
async function test () {
console.log(1);
await func()
console.log(3);
}
test();
console.log(4);
// 最終結(jié)果??: 1 B 4 (永遠(yuǎn)不會(huì)打印3)
// ---------------------或者寫為??-------------------
async function test () {
console.log(1);
await new Promise((resolve) => {
console.log('B')
// resolve() 故意一直保持pending
})
console.log(3);
}
test();
console.log(4);
// 最終結(jié)果??: 1 B 4 (永遠(yuǎn)不會(huì)打印3)
??3:
async function func () {
console.log(2);
return {
then (cb) {
cb()
}
}
}
async function test () {
console.log(1);
await func();
console.log(3);
}
test();
console.log(4);
new Promise((resolve) => {
console.log('B')
resolve()
}).then(() => {
console.log('C')
}).then(() => {
console.log('D')
})
// 最終結(jié)果??: 1 2 4 B C 3 D
步驟拆分??:
同步代碼輸出 1、2await拿到func函數(shù)的具體返回值thenable,將當(dāng)前async函數(shù)內(nèi)的后續(xù)代碼放入微任務(wù)then1(但是需要等待一個(gè)then時(shí)間)同步代碼輸出 4、B,產(chǎn)生log(C)的微任務(wù)then2由于 then1滯后一個(gè)then時(shí)間,直接執(zhí)行then2輸出C,產(chǎn)生log(D)的微任務(wù)then3執(zhí)行原本滯后一個(gè) then時(shí)間的微任務(wù)then1,輸出3執(zhí)行最后一個(gè)微任務(wù) then3輸出D
4、總結(jié)
async函數(shù)返回值
??結(jié)論:
async函數(shù)在拋出返回值時(shí),會(huì)根據(jù)返回值類型開(kāi)啟不同數(shù)目的微任務(wù)
return結(jié)果值:非 thenable、非promise(不等待)return結(jié)果值: thenable(等待 1個(gè)then的時(shí)間)return結(jié)果值: promise(等待 2個(gè)then的時(shí)間)
await右值類型區(qū)別
接非
thenable類型,會(huì)立即向微任務(wù)隊(duì)列添加一個(gè)微任務(wù)then,但不需等待接
thenable類型,需要等待一個(gè)then的時(shí)間之后執(zhí)行接
Promise類型(有確定的返回值),會(huì)立即向微任務(wù)隊(duì)列添加一個(gè)微任務(wù)then,但不需等待
TC 39 對(duì) await后面是promise的情況如何處理進(jìn)行了一次修改,移除了額外的兩個(gè)微任務(wù),在早期版本,依然會(huì)等待兩個(gè)then的時(shí)間
參考資料
https://juejin.cn/post/6844903715342647310#heading-3: https://juejin.cn/post/6844903715342647310#heading-3
作者: Squirrel_
https://juejin.cn/post/7194744938276323384
