答網(wǎng)友問(wèn):Await 一個(gè) Promise 對(duì)象到底發(fā)生了什么
大家好,我是二哥。
前兩篇文章發(fā)出來(lái)后,有一些網(wǎng)友在后臺(tái)咨詢我一些問(wèn)題,我把它們歸總羅列在一起。這篇文章既是答網(wǎng)友問(wèn)也是對(duì)前兩篇的補(bǔ)充和復(fù)習(xí)。
先放下前兩篇的鏈接。
??多圖剖析公式 async=Promise+Generator+自動(dòng)執(zhí)行器??
圖 1:async 函數(shù)代碼示例
問(wèn) 0:上一篇所提到的 generator 和自動(dòng)執(zhí)行器是運(yùn)行在不同的線程里面嗎?
答 0:無(wú)論是 generator 還是自動(dòng)執(zhí)行器,都是在 event-loop 線程也就是運(yùn)行 JS code 的主線程里面運(yùn)行的。再?gòu)?qiáng)調(diào)一遍:它倆不是在兩個(gè)線程里面運(yùn)行的。
讓我們?cè)倏匆槐?Node.js 官網(wǎng)對(duì) event-loop 的描述。它強(qiáng)調(diào)了一個(gè)重點(diǎn):JS code 是以單線程方式被執(zhí)行的。
The event loop is what allows Node.js to perform non-blocking I/O operations — despite the fact that JavaScript is single-threaded — by offloading operations to the system kernel whenever possible.
問(wèn) 1:await p 這條語(yǔ)句產(chǎn)生了異步請(qǐng)求了嗎?
答 1:不,它沒(méi)有。await 只是在等待 p 狀態(tài)的改變,無(wú)論狀態(tài)是從 pending 變成 resolved 還是從 pending 變?yōu)?rejected 。
問(wèn) 2:那異步請(qǐng)求是什么時(shí)候產(chǎn)生的?
答 2:是在 Promise 的 executor 里面,執(zhí)行 setTimeout 時(shí)產(chǎn)生的。
?下文把 new Promise() 時(shí)傳遞進(jìn)去的 callback (resolve, reject)=>{ /* your code */} 稱為 executor 。其中參數(shù) resolve 和 reject 是由 Promise 自己實(shí)現(xiàn)的。需要注意的是這個(gè) executor 是在 new Promise() 的時(shí)候,立即執(zhí)行的。
假如我們?cè)?executor 里面執(zhí)行的是 fs.read(fd[, options], callback) 這樣的語(yǔ)句,那類似地,異步請(qǐng)求是在調(diào)用 fs.read() 時(shí)產(chǎn)生的。?
問(wèn) 3:p 狀態(tài)改變后,為什么通過(guò) resolve(200) 傳遞的 200 會(huì)變成變量 res 的求值結(jié)果?
答 3:這就是為什么說(shuō)我們需要了解 await 背后的實(shí)現(xiàn)原理。我們借助圖 2 和圖 4 來(lái)復(fù)習(xí)一下。
如圖 2 所示,async 函數(shù)首先轉(zhuǎn)換成了 generator 函數(shù)。但 generator 函數(shù)自己是不能自動(dòng)運(yùn)行的,所以得搭配一個(gè)自動(dòng)執(zhí)行器,驅(qū)動(dòng)它往前走。自動(dòng)執(zhí)行器如同慈愛(ài)的媽媽,而 generator 就像那個(gè)懵懂的幼兒。小孩子每走一段路都會(huì)停下來(lái),回頭看看在他身后寸步不離的媽媽,得到媽媽的鼓勵(lì)或者獎(jiǎng)勵(lì)后,再走向下一個(gè)目標(biāo)。
圖 2:async 函數(shù)轉(zhuǎn)換成 generator 函數(shù)示例
在講解圖 4 之前,還是有必要再次復(fù)習(xí)兩個(gè)重要的概念:yield 表達(dá)式和 yield 語(yǔ)句。如圖 3 所示:
- a+b 是表達(dá)式,它的求值結(jié)果影響到的是 { value: xxx, done: xxx } 中的 value 屬性,而 { value: xxx, done: xxx } 是調(diào)用者通過(guò)迭代器調(diào)用 next() 方法的返回值 。
- yield a+b 是 yield 語(yǔ)句,調(diào)用者可以通過(guò)給 next() 方法傳實(shí)參來(lái)影響 yield 語(yǔ)句的返回值。比如 next(200) 則會(huì)使得變量 a1 為 200 。
圖 3 還畫出了一個(gè)重要的地方:generator 函數(shù)執(zhí)行的暫停點(diǎn):在 yield 表達(dá)式求值結(jié)束之后,但 yield 語(yǔ)句返回之前。
圖 3:yield 表達(dá)式和 yield 語(yǔ)句對(duì)比
為了更好更清晰地回答問(wèn)題 3,二哥給大家畫了圖 4 。
① ?這一步開(kāi)始通過(guò)執(zhí)行器調(diào)用 generator。
② 雖然對(duì) generator 真正的調(diào)用發(fā)生在這里,但 generator 函數(shù)在 ② 這步其實(shí)什么都沒(méi)有做,只是立即返回了一個(gè)迭代器。
③ 自動(dòng)執(zhí)行器從這里開(kāi)始進(jìn)入驅(qū)動(dòng) generator 模式。③ 這一步?jīng)]有給形參 data? 賦值,因?yàn)槲覀儾荒茉诘谝淮螆?zhí)行 g.next() 的時(shí)候給它注入一個(gè)值。
④ 這一步每調(diào)用一次 g.next() 就會(huì)使得 generator 從上次暫停于 yield 的位置開(kāi)始運(yùn)行,直到再次遇到 yield 。
⑤ 所以第一次對(duì) g.next() 調(diào)用使得左側(cè) generator 函數(shù)從函數(shù)起始位置一直運(yùn)行直到遇到 yield 。
我們看到 ⑤ 所標(biāo)識(shí)出來(lái)的代碼執(zhí)行過(guò)程其實(shí)是創(chuàng)建了一個(gè) Promise 對(duì)象,且在 Promise 的 executor 里面設(shè)置了一個(gè) 1s 鐘的定時(shí)器。注意,這個(gè) executor 是在創(chuàng)建 Promise 對(duì)象時(shí)立即執(zhí)行的,不過(guò) ⑦ 處的代碼要等到 1s 之后才會(huì)執(zhí)行。
⑥ generator 函數(shù)暫停之前,先會(huì)將 yield 表達(dá)式的求值結(jié)果通過(guò) { value: xxx, done: xxx} 返回給 g.next() 調(diào)用方,也即右圖 ④ 位置。
所以你一定猜到了,右圖 ④ 位置的變量 result 為 { value: p, done: false} 。這里的 p 就是 ⑤ 執(zhí)行過(guò)程中產(chǎn)生的 Promise 對(duì)象。
通過(guò)這樣的方式,Promise 對(duì)象在 generator 函數(shù)和自動(dòng)執(zhí)行器之間流轉(zhuǎn)。真是一個(gè)巧妙的過(guò)程。
那么你在右側(cè) ⑧ 處看到 result.value.then(callback) 這樣的語(yǔ)句就不會(huì)感到納悶了,這是 Promise 的標(biāo)準(zhǔn)用法。當(dāng) p 的狀態(tài)變成 resolved 后,⑧ 處的 callback 自然就會(huì)得到運(yùn)行的機(jī)會(huì)了。
⑦ 1s 很快,滴答一下過(guò)去后,resolve(200) 得以運(yùn)行。它的運(yùn)行使得 p 的狀態(tài)變成 resolved,所以在 ⑧ 處耐心等待的 callback 開(kāi)始了它的工作。
⑧ 是的,這個(gè)時(shí)候 data 的值為 200 。這是再自然不過(guò)的事,如果你對(duì) Promise 的使用了然于胸的話。
⑨ 自動(dòng)執(zhí)行器又一次執(zhí)行 next(data)? 。不過(guò)這一次給它傳了一個(gè)實(shí)參 200 。所以這一次 ④ 處執(zhí)行的代碼變?yōu)椋?nbsp;g.next(200) 。
⑩ 自動(dòng)執(zhí)行器執(zhí)行 g.next(200) 必然會(huì)驅(qū)使 generator 函數(shù)動(dòng)身繼續(xù)往前趕路。
還記得 generator 函數(shù)上次停在哪里休息的嗎?對(duì),左側(cè) ⑤ 處箭頭所指的位置。generator 函數(shù)恢復(fù)運(yùn)行后干的第一件事就是對(duì) yield 語(yǔ)句求值。
如果像 g.next()? 這樣驅(qū)動(dòng)它的話,yield 語(yǔ)句返回的是 undefined 。不過(guò)這次我們不一樣,因?yàn)槲覀儓?zhí)行的是 g.next(200) 。很巧妙,傳給 next() 的實(shí)參 200 作為 yield 語(yǔ)句的返回值賦值給了左側(cè)變量 res? 。
圖 4:generator + 自動(dòng)執(zhí)行器細(xì)節(jié)圖
讓我們?cè)倩仡^看下圖 1 的示例代碼,我們來(lái)做個(gè)總結(jié):
- await p 語(yǔ)句是個(gè)糖衣,它包裹的是 yield p 語(yǔ)句 + 自動(dòng)執(zhí)行器。
- 所謂 await p 暫停并不是說(shuō)主線程執(zhí)行 JS code 暫停了。相反主線程還在繼續(xù)執(zhí)行其它的 JS code 。
- await 是在等待 p 的狀態(tài)發(fā)生變化。這個(gè)等待時(shí)間有多長(zhǎng)?這完全取決于創(chuàng)建 p 的時(shí)候, executor 里面何時(shí)會(huì)調(diào)用 resolve() 或 reject() 。
- 執(zhí)行 await p 語(yǔ)句的時(shí)候,無(wú)論 p 的狀態(tài)是否已經(jīng)發(fā)生了變化,執(zhí)行到 await p 都會(huì)導(dǎo)致 V8 engine 轉(zhuǎn)而去自動(dòng)執(zhí)行器里面執(zhí)行。這是 yield p 語(yǔ)句使然。
- 自動(dòng)執(zhí)行器如同一個(gè)如影隨形的媽媽,她拿到 p 之后,會(huì)耐心地等待,直到得到 p 狀態(tài)改變后的 value 。最后再通過(guò) g.next(value) 把 value 返回給它摯愛(ài)的 generator 函數(shù)。
圖 5:同圖 1