V8 Promise源码全面解读
閱讀本文你將收獲什么?
了解 V8 Promise 源碼全過程,世界上不再有能困住你的 Promise 題目,我就是這么肯定這篇文章的干貨
僅僅了解或者實(shí)現(xiàn)了 Promise/A+ 規(guī)范,這與 JavaScript 的 Promise 中間還有很大的差距
如果你在面試時(shí)將 Promise 回答到本文的深度,一定是收獲 SP 或者 SSP offer 的利器,因?yàn)槊嬖嚬俅蟾怕室膊恢肋@些知識(shí)。
你知道 瀏覽器 & Node 中真正的 Promise 執(zhí)行順序是怎么樣的嗎,如果你只是看過 Promise/A+ 規(guī)范的 Promise 實(shí)現(xiàn),那么我肯定的告訴你,你對 Promise 執(zhí)行順序的認(rèn)知是錯(cuò)誤的。不信的話你就看看下面這兩道題。
Promise.resolve().then(() => {console.log(0);return Promise.resolve(4) }).then(res => {console.log(res); })Promise.resolve().then(() => {console.log(1); }).then(() => {console.log(2); }).then(() => {console.log(3); }).then(() => {console.log(5); }).then(() => {console.log(6); }) // 0 1 2 3 4 5 6new Promise((resolve, reject) => {Promise.resolve().then(() => {resolve({then: (resolve, reject) => resolve(1)});Promise.resolve().then(() => console.log(2));}); }).then(v => console.log(v)); // 2 1按照 Promise/A+ 規(guī)范來說,上面的代碼打印的結(jié)果應(yīng)該是 0 1 2 4 3 5 6,因?yàn)楫?dāng) then 返回一個(gè) Promise 的時(shí)候需要等待這個(gè) Promise 完成后再同步狀態(tài)和值給 then 的結(jié)果。
但是在 V8 甚至 各大支持 Promise 的主流瀏覽器中的執(zhí)行結(jié)果都是 0 1 2 3 4 5 6
要知道, Promise 屬于 JavaScript 中的一部分, 而 JavaScript 中 Promise 的實(shí)現(xiàn)規(guī)范并非來源于 Promise/A+,而是來自 ECMAScript 規(guī)范。
所以要知道這個(gè)問題的答案,我們不能僅僅看 Promise/A+ ,對于代碼的執(zhí)行過程和順序我們的關(guān)注點(diǎn)應(yīng)該是在 ECMAScript 或者 V8 上。
接下來我就結(jié)合 ECMAScript 規(guī)范來對 V8 中 Promise 源碼進(jìn)行全面的解讀。
PromiseState
Promise 的 3 種狀態(tài),pending、 fulfilled 和 rejected ,源碼如下:
// Promise constants extern enum PromiseState extends int31 constexpr 'Promise::PromiseState' {kPending,// 等待狀態(tài)kFulfilled,// 成功狀態(tài)kRejected// 失敗狀態(tài) }一個(gè)新創(chuàng)建的 Promise 處于 pending 狀態(tài)。當(dāng)調(diào)用 resolve 或 reject 函數(shù)后,Promise 處于 fulfilled 或 rejected 狀態(tài),此后 Promise 的狀態(tài)保持不變,也就是說 Promise 的狀態(tài)改變是不可逆的,如果再次調(diào)用 resolve 或者 reject 將不會(huì)發(fā)生任何事,Promise 源碼中出現(xiàn)了多處狀態(tài)相關(guān)的 assert(斷言),這個(gè)就不贅述了,想必對于 Promise 的三種狀態(tài)大家都比較熟悉了。
JSPromise
JSPromise 描述 Promise 的基本信息,源碼如下:
當(dāng) Promise 狀態(tài)改變時(shí),比如調(diào)用了 resolve/reject 函數(shù),SetStatus 方法會(huì)被調(diào)用;Javascript 層調(diào)用 resolve 方法時(shí),reactions_or_result 字段會(huì)被賦值為 resolve 傳入的參數(shù);Javascript 層調(diào)用 then 方法時(shí),說明已經(jīng)有了處理函數(shù),SetHasHandler() 會(huì)被調(diào)用。Status/SetStatus 這兩個(gè)方法一個(gè)獲取 Promise 狀態(tài),一個(gè)設(shè)置 Promise 狀態(tài);
其它
executor:是一個(gè)函數(shù),Promise 構(gòu)造函數(shù)接收的參數(shù),調(diào)用 executor 時(shí)傳入的參數(shù)分別是 resolve 和 reject。
PromiseReaction:是對象,表示 Promise 的處理函數(shù),因?yàn)橐粋€(gè) Promise 多次調(diào)用 then 方法就會(huì)有多個(gè)處理函數(shù),所以底層數(shù)據(jù)結(jié)構(gòu)是個(gè)鏈表,每一個(gè)節(jié)點(diǎn)都存儲(chǔ)著 onFulfilled 和 onRejected 函數(shù)。
復(fù)制代碼
構(gòu)造函數(shù)
構(gòu)造函數(shù)源碼如下:
首先分析兩個(gè) ThrowTypeError,以下代碼可觸發(fā)第一個(gè) ThrowTypeError。
Promise() // Uncaught TypeError: undefined is not a promise原因是沒有使用 new 操作符調(diào)用 Promise 構(gòu)造函數(shù),此時(shí) newTarget 等于 Undefined,觸發(fā)了 ThrowTypeError(MessageTemplate::kNotAPromise, newTarget)。
以下代碼可觸發(fā)第二個(gè) ThrowTypeError。
此時(shí) newTarget 不等于 Undefined,不會(huì)觸發(fā)第一個(gè) ThrowTypeError。但調(diào)用 Promise 構(gòu)造函數(shù)時(shí)沒傳參數(shù) executor,觸發(fā)了第二個(gè) ThrowTypeError。
executor 的類型是函數(shù),在 JavaScript 的世界里,回調(diào)函數(shù)通常是異步調(diào)用,但 executor 是同步調(diào)用。在 Call(context, UnsafeCast(executor), Undefined, resolve, reject) 這一行,同步調(diào)用了 executor。
Promise 構(gòu)造函數(shù)接收的參數(shù) executor,是被同步調(diào)用的
then
ECMAScript 規(guī)范
PromisePrototypeThen
Promise 的 then 方法傳入兩個(gè)回調(diào)函數(shù) onFulfilled 和 onRejected,分別用于處理 fulfilled 和 rejected 狀態(tài),并返回一個(gè)新的 Promise。
JavaScript 層的 then 函數(shù)實(shí)際上是 V8 中的 PromisePrototypeThen 函數(shù),源碼如下:
PromisePrototypeThen 函數(shù)創(chuàng)建了一個(gè)新的 Promise 對象,獲取 then 接收到的兩個(gè)參數(shù),調(diào)用 PerformPromiseThenImpl 完成大部分工作。這里有一點(diǎn)值得注意,then 方法返回的是一個(gè)新創(chuàng)建的 Promise。
const myPromise2 = new Promise((resolve, reject) => {resolve('foo') })const myPromise3 = myPromise2.then(console.log)// myPromise2 和 myPromise3 是兩個(gè)不同的對象 // 有不同的狀態(tài)和不同的處理函數(shù) console.log(myPromise2 === myPromise3) // 打印 falsethen 方法返回的是一個(gè)新的 Promise
PerformPromiseThenImpl
ECMAScript 規(guī)范
PerformPromiseThenImpl 有4個(gè)參數(shù),因?yàn)?PerformPromiseThenImpl 是在調(diào)用 then 調(diào)用,所以它的前三個(gè)參數(shù)分別是被調(diào)用 then 方法的 Promise 對象,以及這個(gè)對象的兩個(gè)處理函數(shù) onFulfilled 和 onRejected,最后一個(gè)參數(shù)為調(diào)用這個(gè) then 返回的新 Promise 對象 resultPromiseOrCapability。
PerformPromiseThenImpl 源碼如下:
PerformPromiseThenImpl 函數(shù)的 pending 分支
PerformPromiseThenImpl 有三個(gè)分支,分別對應(yīng) Promise 的三個(gè)狀態(tài),當(dāng)被調(diào)用 then 方法的 Promise 處于 pending 狀態(tài)時(shí)則進(jìn)入 pending分支。pending 分支調(diào)用 NewPromiseReaction 函數(shù),在接收到的 onFulfilled 和 onRejected 參數(shù)的基礎(chǔ)上,生成 PromiseReaction 對象,存儲(chǔ) Promise 的處理函數(shù),并賦值給 JSPromise 的 reactions_or_result 字段,然后調(diào)用 promise.SetHasHandler() 將 has_handler 設(shè)置為 true(表示這個(gè) Promise 對象已經(jīng)綁定了處理函數(shù))
考慮一個(gè) Promise 可以會(huì)連續(xù)調(diào)用多個(gè) then 的情況,比如:
p 調(diào)用了兩次 then 方法,每個(gè) then 方法都會(huì)生成一個(gè) PromiseReaction 對象。第一次調(diào)用 then 方法時(shí)生成對象 PromiseReaction1,此時(shí) p 的 reactions_or_result 存的是 PromiseReaction1。
第二次調(diào)用 then 方法時(shí)生成對象 PromiseReaction2,調(diào)用 NewPromiseReaction 函數(shù)時(shí),PromiseReaction2.next = PromiseReaction1,PromiseReaction1 變成了 PromiseReaction2 的下一個(gè)節(jié)點(diǎn),最后 p 的 reactions_or_result 存的是 PromiseReaction2。PromiseReaction2 后進(jìn)入 Promise 處理函數(shù)的鏈表,卻是鏈表的頭結(jié)點(diǎn)。NewPromiseReaction 函數(shù)源碼如下:
在 p 處于 pending 狀態(tài)時(shí),p 的 reactions_or_result 字段大致內(nèi)容如下圖。
下圖不是 microtask 隊(duì)列,下圖不是 microtask 隊(duì)列,下圖不是 microtask 隊(duì)列。
圖中使用 onFulfilled 代替 fulfill_handler 是為了方便理解,onRejected也是如此,且只包含于當(dāng)前內(nèi)容相關(guān)的字段,不用太過于糾結(jié)。
PerformPromiseThenImpl 函數(shù)的 fulfilled 分支
fulfilled 分支邏輯則簡單的多,處理的是當(dāng) Promise 處于 fulfilled 狀態(tài)時(shí),調(diào)用 then 方法的邏輯:
先調(diào)用 NewPromiseFulfillReactionJobTask 生成 microtask,然后 EnqueueMicrotask(handlerContext, microtask) 將剛才生成的 microtask 放入 microtask 隊(duì)列,最后調(diào)用 promise.SetHasHandler() 將 has_handler 設(shè)置為 true。
盡管調(diào)用 then 方法時(shí),Promise 已經(jīng)處于 fulfilled 狀態(tài),但 then 方法的 onFulfilled 回調(diào)函數(shù)不會(huì)立即執(zhí)行,而是進(jìn)入 microtask 隊(duì)列等待執(zhí)行。
PerformPromiseThenImpl 函數(shù)的 rejected 分支
rejected 分支邏輯與 fulfilled 分支的邏輯大致相同,但是 rejected 分支中將 onRejected 處理函數(shù)加入 microtask 隊(duì)列之前,會(huì)先判斷當(dāng)前 promise 是否已經(jīng)存在處理函數(shù),如果已經(jīng)存在則會(huì)先調(diào)用
runtime::PromiseRevokeReject(promise),最后調(diào)用 promise.SetHasHandler() 將 has_handler 設(shè)置為 true。
這里的runtime::PromiseRevokeReject(promise) 就是 ECMAScript 規(guī)范 中的 HostPromiseRejectionTracker(promise, “handle”),HostPromiseRejectionTracker 是一個(gè)抽象方法,這表示沒有規(guī)定它的具體的邏輯。大致的作用是標(biāo)記一下 promise 已經(jīng)綁定了 rejected 狀態(tài)的處理函數(shù)。不用疑惑為什么要這么做,后面會(huì)單獨(dú)重點(diǎn)說。
注意 1 HostPromiseRejectionTracker 在兩種情況下被調(diào)用:
當(dāng)一個(gè) promise 在沒有任何處理程序的情況下被拒絕時(shí),它的操作參數(shù)設(shè)置為“reject”。
當(dāng)?shù)谝淮螌⑻幚沓绦蛱砑拥奖痪芙^的 Promise 中時(shí),將調(diào)用它并將其操作參數(shù)設(shè)置為“handle”。
引至 ——ECMAScript 規(guī)范(作者翻譯)
小結(jié)
當(dāng)一個(gè) Promise 被調(diào)用 then 方法時(shí),會(huì)創(chuàng)建一個(gè)新的 Promise 對象 resultPromise
然后根據(jù)當(dāng)前 promise 的不同狀態(tài)執(zhí)行不同的邏輯
pending狀態(tài):會(huì)將 then 傳遞的兩個(gè)處理函數(shù)變成一個(gè) PromiseReaction 節(jié)點(diǎn)插入到promise.reactions_or_result 頭部(PromiseReaction是一個(gè)鏈表結(jié)構(gòu)),這個(gè)步驟就是在搜集依賴,等待 promise 狀態(tài)完成時(shí)再觸發(fā)。
fulfilled狀態(tài):會(huì)創(chuàng)建一個(gè) microtask 來調(diào)用傳入的 onFulfilled 處理函數(shù),并將 reactions_or_result 作為調(diào)用的參數(shù)(此時(shí) reactions_or_result 是 promise 的值,也就是調(diào)用 resolve 時(shí)傳入的參數(shù) value ),并將其插入 microtask 隊(duì)列。
rejected狀態(tài):與 fulfilled 狀態(tài)類似,會(huì)將創(chuàng)建一個(gè) microtask 來調(diào)用傳入的 onRejected 處理函數(shù),并將 reactions_or_result 作為調(diào)用的參數(shù),如果當(dāng)前 Promise 不存在處理函數(shù)(也就是 fulfilled 狀態(tài)的Promsie 首次被調(diào)用 then 方法),會(huì)將其標(biāo)記為已經(jīng)綁定 onRejected 函數(shù),然后將其 microtask 插入 microtask 隊(duì)列。
調(diào)用 promise.SetHasHandler() 將 Promise 的 has_handler 設(shè)置為 true,表示其被調(diào)用的 then 方法綁定了處理函數(shù)。
最后返回新的 Promise 對象。
再來回顧一下 reactions_or_result 的3個(gè)值狀態(tài)(空、鏈表、promise的值):
當(dāng) promise 剛剛被創(chuàng)建時(shí),reactions_or_result的值的空,
當(dāng)promise的狀態(tài)改變?yōu)?fulfilled/rejected 時(shí),其值是調(diào)用對應(yīng) resolve(value)/reject(value) 函數(shù)傳入的參數(shù) value,也就是 promise 的值。
當(dāng) promise 為 pending 狀態(tài)且被調(diào)用 then 后,reactions_or_result 為一個(gè)鏈表,鏈表的每一項(xiàng)存儲(chǔ)的是調(diào)用 then 時(shí)傳入的處理函數(shù)。
上述代碼 5s 后執(zhí)行 resolve 函數(shù),控制臺(tái)打印 fulfilled。
FulfillPromise
ECMAScript 規(guī)范
reslove(value) 就是規(guī)范中的 FulfillPromise(promise, value) 函數(shù),他的作用是將一個(gè) Promise 的狀態(tài)由 pending 改變?yōu)?fulfilled,并且將這個(gè) Promise 的所有處理函數(shù)都變成 microtask 加入到 microtask 隊(duì)列中等待執(zhí)行。
resolve 函數(shù)歸根到底調(diào)用了 V8 的 FulfillPromise 函數(shù),源碼如下:
FulfillPromise 的邏輯是獲取 Promise 的處理函數(shù)到 reactions,reactions 的類型是 PromiseReaction,是個(gè)鏈表,忘記的同學(xué)可以回看上面的那張鏈表圖片;設(shè)置 promise 的 reactions_or_result 為 value,這個(gè) value 就是 JavaScript 層傳給 resolve 的參數(shù);調(diào)用 promise.SetStatus(PromiseState::kFulfilled) 設(shè)置 promise 的狀態(tài)為 fulfilled,最后調(diào)用 TriggerPromiseReactions 來將 reactions 中的處理函數(shù)添加到 microtask 隊(duì)列。
TriggerPromiseReactions
源碼如下:
TriggerPromiseReactions 做了兩件事:
反轉(zhuǎn) reactions 鏈表,前文有分析過 then 方法的實(shí)現(xiàn),then 方法的參數(shù)最終存在鏈表中。最后被調(diào)用的 then 方法,它接收的參數(shù)被包裝后會(huì)位于鏈表的頭部,這不符合規(guī)范,所以需要反轉(zhuǎn)
遍歷 reactions 對象,調(diào)用 MorphAndEnqueuePromiseReaction 將每個(gè)元素放入 microtask 隊(duì)列
MorphAndEnqueuePromiseReaction
MorphAndEnqueuePromiseReaction 將 PromiseReaction 轉(zhuǎn)為 microtask,最終插入 microtask 隊(duì)列,morph 本身有轉(zhuǎn)變/轉(zhuǎn)化的意思,比如 Polymorphism (多態(tài))。
MorphAndEnqueuePromiseReaction 接收 3 個(gè)參數(shù),PromiseReaction 是前面提到的包裝了 Promise 處理函數(shù)的鏈表對象,argument 是 resolve/reject 的參數(shù),reactionType 表示 Promise 最終的狀態(tài),fulfilled 狀態(tài)對應(yīng)的值是 kPromiseReactionFulfill,rejected 狀態(tài)對應(yīng)的值是 kPromiseReactionReject。
MorphAndEnqueuePromiseReaction 的邏輯很簡單,因?yàn)榇藭r(shí)已經(jīng)知道了 Promise 的最終狀態(tài),所以可以從 promiseReaction 對象得到 promiseReactionJobTask 對象,promiseReactionJobTask 的變量命名與 ECMAScript 規(guī)范相關(guān)描述一脈相承,其實(shí)就是傳說中的 microtask。MorphAndEnqueuePromiseReaction 源碼如下,僅保留了和本小節(jié)相關(guān)的內(nèi)容。
MorphAndEnqueuePromiseReaction 的功能很簡單,就是根據(jù) Promise 的狀態(tài)選取 onFulfilled 還是 onRejected 放到 microtask 隊(duì)列準(zhǔn)備執(zhí)行。這里走的是 fulfilled 分支,所以選取的是 onFulfilled。
const myPromise4 = new Promise((resolve, reject) => {setTimeout(_ => {resolve('my code delay 1000') }, 1000) })myPromise4.then(result => {console.log('第 1 個(gè) then') })myPromise4.then(result => {console.log('第 2 個(gè) then') }) // 打印順序: // 第 1 個(gè) then // 第 2 個(gè) then // 如果把 TriggerPromiseReactions 中鏈表反轉(zhuǎn)的代碼注釋掉,打印順序?yàn)?// 第 2 個(gè) then // 第 1 個(gè) then小結(jié)
resolve 只會(huì)處理狀態(tài)為 pending 的 Promise,會(huì)將 Promise 的 reactions_or_result 設(shè)置為傳入的 value,用來作為 Promise 的值,并且會(huì)將 Promise 的狀態(tài)修改為 fulfilled。
因?yàn)?promise 在使用 then 收集依賴時(shí)是將最新的依賴存放到鏈表頭部,所以還需要先對鏈表進(jìn)行反轉(zhuǎn),然后將其挨個(gè)放入 microtask 隊(duì)列中等待執(zhí)行
resolve 的主要工作是遍歷上節(jié)調(diào)用 then 方法時(shí)收集到的依賴,放入 microtask 隊(duì)列中等待執(zhí)行。
reject reject 與 reslove 沒什么太大差別 ECMAScript 規(guī)范 new Promise((resolve, reject) => {setTimeout(_ => reject('rejected'), 5000) }).then(_ => {console.log('fulfilled') }, reason => {console.log(reason) })上述代碼 5s 后執(zhí)行 reject 函數(shù),控制臺(tái)打印 rejected。
RejectPromise
ECMAScript 規(guī)范
reject(season) 函數(shù)調(diào)用了 V8 的 RejectPromise(promise, season) 函數(shù),源碼如下 :
HostPromiseRejectionTracker
與 ReslovePromise 相比,RejectPromise 中多出一個(gè)判斷 Promsie 是否綁定了處理函數(shù)的判斷,如果沒有綁定處理函數(shù)則會(huì)先執(zhí)行 runtime::RejectPromise(promise, reason, debugEvent),這是其實(shí)是 ECMAScript 規(guī)范中的 HostPromiseRejectionTracker(promise, “reject”) ,這已經(jīng)是第二次提到 HostPromiseRejectionTracker了。
在 PerformPromiseThenImpl 函數(shù)的 rejected 分支 處有提到過一次。
在 ECMAScript 規(guī)范中 HostPromiseRejectionTracker 是一個(gè)抽象方法,他甚至沒有明確的執(zhí)行過程,好在規(guī)范中描述了他的作用。
HostPromiseRejectionTracker 用于跟蹤 Promise 的 rejected,例如全局的 rejectionHandled 事件就是由它實(shí)現(xiàn)。
注1
HostPromiseRejectionTracker 在兩種情況下被調(diào)用:
當(dāng)一個(gè) Promise 在沒有任何處理函數(shù)的情況下被調(diào)用 reject 時(shí),調(diào)用它并且第二個(gè)參數(shù)傳遞 “reject”。
當(dāng)?shù)谝淮螢?rejected 狀態(tài)的 Promise 綁定處理函數(shù)時(shí),調(diào)用它并且第二個(gè)參數(shù)傳遞 “handle”。
所以在這里,當(dāng)傳遞 “handle” 就相對于為這個(gè) Promise 對象標(biāo)記為已經(jīng)綁定了處理函數(shù),當(dāng)傳遞 “reject” 相對于標(biāo)記這個(gè) Promise 對象還沒有處理函數(shù)。
我們先來看幾段代碼看看他的實(shí)際作用
當(dāng)我們調(diào)用一個(gè) Promise 的狀態(tài)為 reject 且未為其綁定 onRejected 的處理函數(shù)時(shí), JavaScript會(huì)拋出錯(cuò)誤
并且檢測是否綁定處理函數(shù)是一個(gè)異步的過程
console.log(1); const myPromise1 = new Promise((resolve, reject) => {reject() }) console.log(2); // 1 // 2 // 報(bào)錯(cuò)我們可以為其綁定一個(gè) onRejected 處理函數(shù)來解決我們報(bào)錯(cuò)
const myPromise1 = new Promise((resolve, reject) => {reject() })// 得到一個(gè) rejected 狀態(tài)的 Promise myPromise1.then(undefined, console.log)你一定會(huì)疑惑,Promise 是在何時(shí)檢測它是否綁定了 onRejected 處理函數(shù),如何檢測的?
這就是 HostPromiseRejectionTracker 的作用,在 ECMAScript 規(guī)范中還提到,當(dāng)調(diào)用 HostPromiseRejectionTracker(promise, ‘reject’) 時(shí), 如果 promsie 不存在處理函數(shù),則會(huì)為其設(shè)置一個(gè)處理函數(shù)。
回到上面的邏輯,當(dāng)一個(gè) Promise 的 reject 函數(shù)被調(diào)用時(shí), 如果沒有 onRejected 處理函數(shù),則會(huì)調(diào)用 runtime::RejectPromise 來為其添加一個(gè)處理函數(shù),然后后面會(huì)調(diào)用 TriggerPromiseReactions 將這個(gè)處理函數(shù)加入到 microtask 隊(duì)列,這個(gè)處理函數(shù)時(shí)做的事情就是再次檢測 Promise 是否被綁定了新的 onRejected(也就是有沒有在此期間執(zhí)行了 HostPromiseRejectionTracker(promise, ‘handle’) ),如果沒有則拋出錯(cuò)誤,如果有則什么也不發(fā)生。
所以在對一個(gè) reject 狀態(tài)的 Promise 調(diào)用 then 方法時(shí)需要對其調(diào)用 runtime::PromiseRevokeReject(promise) 來表示這個(gè) Promise 綁定了新的 onRejected,防止錯(cuò)誤被拋出。
所以你必須要趕在這個(gè)檢測的 microtask 執(zhí)行之前綁定處理函數(shù)才能防止這個(gè)錯(cuò)誤的拋出。
注意: 瀏覽器控制臺(tái)有一個(gè)非常奇怪的特性,如果在這個(gè)錯(cuò)誤輸出后在為其綁定 onrejected 處理函數(shù),瀏覽器會(huì)將控制臺(tái)的錯(cuò)誤覆蓋掉。所以如果你在瀏覽器執(zhí)行這段代碼,請將setTimeout的時(shí)間設(shè)置長一點(diǎn),這樣效果更加容易肉眼可見,或者切換到 node 環(huán)境中來運(yùn)行。
小結(jié)
reject 和 resolve 的邏輯基本相同,分為 4 步:
設(shè)置 Promise 的 reason,也就是 reject 的參數(shù)
設(shè)置 Promise 的狀態(tài):rejected
如果 Promise 沒有 onRejected 處理函數(shù),則會(huì)為其添加一個(gè)再次檢測 Promise 是否綁定 onRejected 的處理函數(shù)
從之前調(diào)用 then/catch 方法時(shí)收集到的依賴,也就是 promiseReaction 對象,得到一個(gè)個(gè) microtask,最后將 microtask 插入 microtask 隊(duì)列
PromisePrototypeCatch
以上面代碼為例,當(dāng) catch 方法執(zhí)行時(shí),調(diào)用了 V8 的
PromisePrototypeCatch 方法,源碼如下: transitioning javascript builtin PromisePrototypeCatch(js-implicit context: Context, receiver: JSAny)(onRejected: JSAny): JSAny {const nativeContext = LoadNativeContext(context);return UnsafeCast<JSAny>(InvokeThen(nativeContext, receiver, Undefined, onRejected)); }PromisePrototypeCatch 的源碼確實(shí)只有就這幾行,除了調(diào)用 InvokeThen 方法再無其它 。
InvokeThen
從名字可以推測出,InvokeThen 調(diào)用的是 Promise 的 then 方法,InvokeThen 源碼如下:
transitioning macro InvokeThen<F: type>(implicit context: Context)(nativeContext: NativeContext, receiver: JSAny, arg1: JSAny, arg2: JSAny,callFunctor: F): JSAny {if (!Is<Smi>(receiver) &&IsPromiseThenLookupChainIntact(nativeContext, UnsafeCast<HeapObject>(receiver).map)) {const then =UnsafeCast<JSAny>(nativeContext[NativeContextSlot::PROMISE_THEN_INDEX]);// 重點(diǎn)在下面一行,調(diào)用 then 方法并返回,兩個(gè)分支都一樣return callFunctor.Call(nativeContext, then, receiver, arg1, arg2);} elsedeferred {const then = UnsafeCast<JSAny>(GetProperty(receiver, kThenString));// 重點(diǎn)在下面一行,調(diào)用 then 方法并返回,兩個(gè)分支都一樣return callFunctor.Call(nativeContext, then, receiver, arg1, arg2);} }InvokeThen 方法有 if/else 兩個(gè)分支,兩個(gè)分支的邏輯差不多,本小節(jié)的 JS 示例代碼走的是 if 分支。先是拿到 V8 原生的 then 方法,然后通過 callFunctor.Call(nativeContext, then, receiver, arg1, arg2) 調(diào)用 then 方法。then 方法之前有介紹,這里不再贅述。
既然 catch 方法底層調(diào)用了 then 方法,那么 catch 方法也有和 then 方法一樣的返回值,catch 方法可以繼續(xù)拋出異常,可以繼續(xù)鏈?zhǔn)秸{(diào)用。
上面的代碼第 2 個(gè) catch 捕獲第 1 個(gè) catch 拋出的異常,最后打印 last catch。
小結(jié)
catch 方法通過底層調(diào)用 then 方法來實(shí)現(xiàn)
假如 obj 是一個(gè) Promise 對象,JS 層面 obj.catch(onRejected) 等價(jià)于 obj.then(undefined, onRejected)
then 的鏈?zhǔn)秸{(diào)用與 microtask 隊(duì)列
以上代碼運(yùn)行后,打印 Error: 456 和 undefined。為了便于敘述,將 then 的鏈?zhǔn)秸{(diào)用寫法改為啰嗦寫法。
const p0 = Promise.resolve('123') const p1 = p0.then(() => {throw new Error('456')}) const p2 = p1.then(_ => {console.log('shouldnot be here') }) const p3 = p2.catch((e) => console.log(e)) const p4 = p3.then((data) => console.log(data));then 方法返回新的 Promise,所以 p0、p1、p2、p3 和 p4 這 5 個(gè) Promise 互不相等。
當(dāng)一個(gè) Promise 處于 rejected 狀態(tài)時(shí),如果找不到 onRejected 處理函數(shù)則會(huì)將 rejected 的狀態(tài)和其值往下傳遞,直到找到為止。(resolve也是一樣),這個(gè)過程后面會(huì)介紹
catch 方法的作用就是綁定 onRejected 函數(shù)
microtask 的執(zhí)行
所有同步代碼執(zhí)行完畢,開始執(zhí)行取 microtask 隊(duì)列中的 microtask 執(zhí)行,核心方法是 MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask ,由于 microtask 的類型由很多種,所以 RunSingleMicrotask 的分支有許多。這里就不列出代碼了。
PromiseReactionJob
在執(zhí)行 microtask 的過程中,MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask 會(huì)調(diào)用 PromiseReactionJob,源碼如下:
PromiseReactionJob 中會(huì)判斷當(dāng)前任務(wù)是否存在需要執(zhí)行的處理函數(shù),如果不存在則直接將上一個(gè) Promise 的值作為參數(shù)調(diào)用 FuflfillPromiseReactionJob ,如果存在則執(zhí)行這個(gè)處理函數(shù),將執(zhí)行結(jié)果當(dāng)做參數(shù)調(diào)用 FuflfillPromiseReactionJob。
也就是說,只要一個(gè) Promise 的 onFulfilled 或者 onRejected 在執(zhí)行過程中只要沒有拋出異常,這個(gè) Promise 就會(huì)執(zhí)行 FuflfillPromiseReactionJob 將狀態(tài)修改為 fulfilled。如果拋出異常則執(zhí)行 RejectPromiseReactionJob。
注意:FuflfillPromiseReactionJob 做的事情很多,執(zhí)行 resolve 只是其中的一個(gè)分支
我們來看看 FuflfillPromiseReactionJob 具體做了哪些事情。
FuflfillPromiseReactionJob
源碼如下:
FuflfillPromiseReactionJob 有3個(gè)分支,這里走的是第一個(gè)分支,調(diào)用 ResolvePromise,這個(gè)方法很重要,他是規(guī)范中的 Promise Resolve Functions,他的作用是同步當(dāng)前處理函數(shù)的結(jié)果(值和狀態(tài))給其產(chǎn)生的promsie。promiseOrCapability。
上面例子中 promiseOrCapability 就是 p1, 值是 2
ResolvePromise
這是一個(gè)很重要的方法,基本上每一個(gè) Promise 的狀態(tài)需要變成 fulfilled 都會(huì)調(diào)用它,它的邏輯也產(chǎn)生了許多 PromiseA+ 中沒有的特性。 下面的代碼我刪除了不重要的部分
ResolvePromise 方法中有幾個(gè)很重要的邏輯,一個(gè)是調(diào)用 FulfillPromise,這個(gè)在resolve的時(shí)候已經(jīng)介紹過了,作用是修改 promise 的狀態(tài)為 fulfilled 并為其設(shè)置值,然后將 promise 的處理函數(shù)推到微任務(wù)隊(duì)列。
let p0 = Promise.resolve() let p1 = p0.then(() => {return 1; })p1.then(console.log)// p0 then 中 onFulfilled 回調(diào)進(jìn)入隊(duì)列 // PromiseReactionJob 中調(diào)用 p0 的 onFulfilled ,得到結(jié)果為1 // 調(diào)用 FuflfillPromiseReactionJob ,然后調(diào)用 ResolvePromise // ResolvePromise 做如下操作 // 將 p1 變成 fulfilled, 并將 p1 的處理函數(shù) console.log 加到隊(duì)列,參數(shù)為 1 // p1 的 onFulfilled 出隊(duì)列執(zhí)行,輸出 1 復(fù)制代碼 還有一種情況就是 當(dāng) resolution 的值是一個(gè) Promise 對象或者是一個(gè)包含 then 方法的對象時(shí)。會(huì)調(diào)用 NewPromiseResolveThenableJobTask 生成一個(gè) microtask,然后將其加入 microtask 隊(duì)列中。 let p0 = Promise.resolve() // 兩種特殊情況 let p1 = p0.then(() => {return Promise.resolve(1);// 返回值是 Promise 對象 }) let p2 = p0.then(() => {return {then(resolve, reject){resolve(1)};// 返回值包含 then 方法 })p1.then(console.log)NewPromiseResolveThenableJobTask
NewPromiseResolveThenableJobTask 的目的是調(diào)用 resolution 的 then 方法,在回調(diào)函數(shù)中同步狀態(tài)給 promise。 這可能不是很好理解,我把他轉(zhuǎn)化為js來大致就是這樣的。
這個(gè)任務(wù)中會(huì)調(diào)用 resolution.then ,然后同步到 promsie。但是這個(gè)整體的過程需要加入 microtask 隊(duì)列中等待運(yùn)行,當(dāng)這個(gè)任務(wù)運(yùn)行時(shí),如果 resolution 也是一個(gè) Promise 的話,則 (value) => {ReslovePromise(promise, value) } 又會(huì)被作為一個(gè) microtask 加入 microtask 隊(duì)列中等待運(yùn)行。
你可以會(huì)疑惑為什么要這樣做?為什么不同步調(diào)用 resolution.then((value) => {ReslovePromise(promise, value) }),而是把他封裝為一個(gè) microtask 呢?我一開始也感到疑惑,好在規(guī)范中給出了一個(gè)原因。
注意: 此作業(yè)使用提供的 thenable 及其 then 方法來解決給定的 Promise。 此過程必須作為作業(yè)進(jìn)行,以確保在對任何周圍代碼的評(píng)估完成后對 then 方法進(jìn)行評(píng)估。
引至 ECMAScript NewPromiseResolveThenableJobTask 規(guī)范(作者翻譯)
什么是 thenable:
Javascript 為了識(shí)別 Promise 產(chǎn)生的一個(gè)概念,簡單來說就是所有包含 then 方法的對象都是 thenable。
『以確保在對任何周圍代碼的評(píng)估完成后對 then 方法進(jìn)行評(píng)估』指的是什么呢?我唯一能想到的就是下面這種情況。
const p1 = new Promise((resolve, reject) => {const p2 = Promise.resolve().then(() => {resolve({then: (resolve, reject) => resolve(1)});const p3 = Promise.resolve().then(() => console.log(2));}); }).then(v => console.log(v)); // 2 1上面 p2 的 onFulfilled回調(diào) 會(huì)先進(jìn)入 microtask 隊(duì)列,等待其執(zhí)行時(shí) 調(diào)用 p1 的 resolve,但是參數(shù)是一個(gè)包含 then 方法的對象。這時(shí) p1 不會(huì)立即改變?yōu)?fulfilled,而是創(chuàng)建一個(gè) microtask 來執(zhí)行這個(gè)then方法,然后將 p2的 onFulfilled 加入 microtask 隊(duì)列。這時(shí) microtask 隊(duì)列中有兩個(gè) microtask,一個(gè)是執(zhí)行 resolve 返回值中的 then函數(shù),另一個(gè)則是 p3的 onFulfilled 函數(shù)。
然后取出第一個(gè) microtask 執(zhí)行(取出后 microtask 隊(duì)列中只剩下 p3的 onFulfilled),執(zhí)行后 p1 的狀態(tài)變?yōu)?fulfilled,然后 p1 的 onFulfilled 進(jìn)入隊(duì)列。后面可想而知是相繼輸出 2和1(因?yàn)?p1 的 onFulfilled 函數(shù)在 p3 的 onFulfilled 函數(shù)之后進(jìn)入 microtask 隊(duì)列)。
如果沒有將 NewPromiseResolveThenableJobTask 作為一個(gè) microtask。也就變成了 p2.then 中的回調(diào)執(zhí)行時(shí)同步觸發(fā) resolve 參數(shù)中的 then 方法,fulfilled 的狀態(tài)會(huì)立即同步到 p1,這時(shí) p1 的 onFulfilled 就會(huì)先進(jìn)入 microtask,導(dǎo)致結(jié)果變?yōu)?12。這樣的執(zhí)行結(jié)果可以會(huì)讓JavaScript開發(fā)者感到疑惑。
所以 ECMAScript 將其作為一個(gè)異步任務(wù)來執(zhí)行。
似乎 返回 Promsie 對象會(huì)產(chǎn)生兩個(gè) microtask 似乎會(huì)更讓人感到疑惑。
RejectPromiseReactionJob
PromiseReactionJob 中如果處理函數(shù) handler 執(zhí)行時(shí)拋出異常則會(huì)執(zhí)行 RejectPromiseReactionJob,也就是下面這種情況
這是會(huì)調(diào)用 RejectPromiseReactionJob,其源碼如下
macro RejectPromiseReactionJob(context: Context,promiseOrCapability: JSPromise|PromiseCapability|Undefined, reason: JSAny,reactionType: constexpr PromiseReactionType): JSAny {if constexpr (reactionType == kPromiseReactionReject) {typeswitch (promiseOrCapability) {case (promise: JSPromise): { // promiseOrCapability 就是 p1,是一個(gè) Promise 對象 // 執(zhí)行 RejectPromise,調(diào)用 p1 的 reject 方法return RejectPromise(promise, reason, False);}case (Undefined): {return Undefined;}case (capability: PromiseCapability): {const reject = UnsafeCast<Callable>(capability.reject);return Call(context, reject, Undefined, reason);}}} else {StaticAssert(reactionType == kPromiseReactionFulfill);return PromiseRejectReactionJob(reason, Undefined, promiseOrCapability);} }RejectPromiseReactionJob 與 FuflfillPromiseReactionJob 是類似的,就是調(diào)用 RejectPromise 來調(diào)用 Promsie 的 reject 方法,這個(gè)在上面 reject 的地方介紹過了。
PromiseReactionJob的handler == Undefined分支
PromiseReactionJob 中還有一個(gè) handler == Undefined 的分支也很重要,當(dāng)一個(gè) task 中的 handler 為 undefined時(shí)會(huì)進(jìn)入這個(gè)分支,為了方便閱讀,這里再貼一下代碼
進(jìn)入分支后會(huì)直接獲取上一個(gè) Promise 對象的 value 和 狀態(tài) 同步到當(dāng)前 promise 來,我們來通過一段js了解他
let p0 = new Promise((resolve, reject) => {reject(123) }) // p0 的狀態(tài)為 rejectedlet p1 = p0.then(_ => {console.log('p0 onFulfilled')}) // p0 的 onRejected 作為 handler 進(jìn)入 microtask 隊(duì)列 // 但是因?yàn)?then 沒有傳遞第二個(gè)參數(shù) // 所以 onRejected 是 undefined,那么 handler 也是 undefinedlet p2 = p1.then(_ => {console.log('p1 onFulfilled')}) /* 為p1綁定 PromiseReaction{onFulfilled:_ => {console.log('p1 onFulfilled')}, onRejected:undefined } */ let p3 = p2.then(_ => {console.log('p2 onFulfilled')}, _ => {console.log('p2 onRejected')}) /* 為p2綁定 PromiseReaction{onFulfilled:_ => {console.log('p2 onFulfilled')}, onRejected:_ => {console.log('p2 onRejected') } */ let p4 = p3.then(_ => {console.log('p3 onFulfilled')}, _ => {console.log('p3 onRejected')}) /* 為p3綁定 PromiseReaction{onFulfilled:_ => {console.log('p3 onFulfilled')}, onRejected:_ => {console.log('p3 onRejected') } *///p2 onRejected //p3 onFulfilled同步代碼執(zhí)行完畢后(執(zhí)行過程大致如注釋),開啟取 microtask 執(zhí)行,此時(shí) microtask 隊(duì)列中只有一個(gè) handler 為 undefined 的任務(wù)。進(jìn)入 PromiseReactionJob 的 handler == Undefined 分支。
因?yàn)榇藭r(shí) p0 狀態(tài)為 rejected,所以執(zhí)行 RejectPromiseReactionJob(context, promiseOrCapability, argument, reactionType),其中 promiseOrCapability 就是p1, argument 就是 p0 的值 123,reactionType 為 rejected。
執(zhí)行后 p0 的狀態(tài)也變?yōu)?reactionType 也就是 rejected,p1 的值為 argument(相當(dāng)于吧p0的狀態(tài)和值都轉(zhuǎn)移到了p1)。
然后執(zhí)行 p1 的 reject 函數(shù)(FulfillPromise(p1, 123)),會(huì)吧 p1 綁定的 PromiseReaction 鏈表中的 onRejected(還是undefined) 當(dāng)做 handler 進(jìn)入microtask 隊(duì)列(因?yàn)?p1 的狀態(tài)是 rejected,所以是onRejected)
同樣還是取 microtask 任務(wù)執(zhí)行,handler 還是 undefined,后面就和上面一樣,把狀態(tài) rejected 和值 123 繼續(xù)同步給 p2,…
再次取 microtask 執(zhí)行,因?yàn)?p2 綁定了 onRejected 函數(shù),所以 handler 不是 undefined,則不走 handler == Undefined 分支,另一個(gè)分支的邏輯剛剛已經(jīng)描述過了。大概就是執(zhí)行 onRejected(123),然后將其結(jié)果設(shè)置到 p3 的value,p3 變?yōu)?fulfilled 狀態(tài)。
輸出 p2 onRejected
因?yàn)?onRejected(123) 的返回值是 undefined,所以 p3 變?yōu)?fulfilled 狀態(tài),且值為 undefined
后面還是一樣的,但是 handler 就是 onFulfilled 了,因?yàn)?p3 的狀態(tài)是 fulfilled嘛,這里就相當(dāng)于 onFulfilled(undefined)(因?yàn)?p3 的值的 undefined)。
輸出 p3 onFulfilled
而后 p4 的狀態(tài)也變成了 fulfilled,值也是 undefined,因?yàn)?p3 的 onFulfilled 返回值是 undefined
然后 p4 的 onFulfilled 變成 handler 隊(duì)列,因?yàn)?p4 沒有調(diào)用 then 綁定過 onFulfilled 處理函數(shù)。但是因?yàn)闆]有調(diào)用 then 方法,所以也沒有產(chǎn)生新的 Promsie 對象,這次在執(zhí)行 FuflfillPromiseReactionJob 方法的時(shí)候進(jìn)入 promiseOrCapability 為 Undefined 分支就結(jié)束了
至此所有相關(guān)的任務(wù)全部執(zhí)行完成
如果上面你看懂了,那么下面這段代碼我想你也應(yīng)該能知道結(jié)果
catch(onRejected) 的本質(zhì)是 then(undefined, onRejected)
這就是 Promise 的 rejected 傳遞機(jī)制,不斷向下傳遞直到遇見 onRejected 處理函數(shù)為止
Promise 的幾個(gè)高難度題目
題目1
主要考察 當(dāng) Promise 的值是 promsie 對象時(shí)會(huì)如何處理,在本文 的 then 的鏈?zhǔn)秸{(diào)用與 microtask 隊(duì)列> ResolvePromise 目錄末尾處開始介紹
關(guān)鍵字:thenable、NewPromiseResolveThenableJobTask
題解
為了方便描述,我們將上面的代碼轉(zhuǎn)化為下面這樣
先執(zhí)行所有的同步代碼,執(zhí)行過程如下面的注釋
let p1 = Promise.resolve() // 1. p1 的狀態(tài)為 fulfilledlet p2 = p1.then(() => {console.log(0);let p3 = Promise.resolve(4)return p3; }) // 2. 因?yàn)?p1 的狀態(tài)已經(jīng)是 fulfilled,所以調(diào)用 then 后立即將 onFulfilled 放入 microtask 隊(duì)列 // 此時(shí) microtask 只有p1的 onFulfilled: [p1.onFulfilled]let p4 = p2.then((res) => {console.log(res) }) // 3. p2的狀態(tài)還是 pending,所以調(diào)用 then 后是為 p2 收集依賴,此時(shí) p2 的 reactions 如下 /*{onFulfilled: (res) => {console.log(res)},onRejected: undefined }*/let p5 = Promise.resolve() // 4. p5 的狀態(tài)為 fulfilledlet p6 = p5.then(() => {console.log(1); }) // 5. 同第2步,將 onFulfilled 加入 microtask 隊(duì)列 // 此時(shí) microtask 是: [p1.onFulfilled, p5.onFulfilled]let p7 = p6.then(() => {console.log(2); }) // 6. 同第3步,是給 p6 添加 reactionslet p8 = p7.then(() => {console.log(3); }) // 7. 同上,是給 p7 添加 reactionslet p9 = p8.then(() => {console.log(5); }) // 8. 同上,是給 p8 添加 reactionslet p10 = p9.then(() => {console.log(6); }) // 9. 同上,是給 p9 添加 reactions當(dāng)同步代碼執(zhí)行完成后,microtask 隊(duì)列只有
[p1.onFulfilled, p5.onFulfilled]然后取出 p1.onFulfilled 來執(zhí)行,此時(shí)輸出 0,但是發(fā)現(xiàn) p1.onFulfilled 返回值的 p3 是一個(gè) Promise 對象。所以會(huì)執(zhí)行 ResolvePromise 的 Enqueue 代碼塊,里面會(huì)調(diào)用 NewPromiseResolveThenableJobTask 產(chǎn)生一個(gè)微任務(wù),這個(gè)微任務(wù)的要做的事情上面已經(jīng)介紹過,大致就是下面這樣
let promiseResolveThenableJobTask = () => {p3.then((value) => { // p3的value是4ReslovePromise(p2, value) }) }然后將其加入 microtask 隊(duì)列, 這時(shí) microtask 隊(duì)列就變成了 :
[p5.onFulfilled, promiseResolveThenableJobTask]繼續(xù)取出 p5.onFulfilled 執(zhí)行,此時(shí)輸出 1,因?yàn)?p5.onFulfilled 返回值是 undefined,所以就將 undefined 作為 p6 的值,然后將 p6 的狀態(tài)變?yōu)?fulfilled。
因?yàn)?p6 的狀態(tài)被改變,所以它的 reactions 也會(huì)加入 microtask 隊(duì)列,這時(shí) microtask 隊(duì)列就變成這樣:
[promiseResolveThenableJobTask,p6.onFulfilled]復(fù)制代碼
同樣是取 promiseResolveThenableJobTask 執(zhí)行,因?yàn)?promiseResolveThenableJobTask 的內(nèi)容是下面這樣
let promiseResolveThenableJobTask = () => {p3.then((value) => { ReslovePromise(p2, value) // ReslovePromise 的作用上面有介紹}) }所以執(zhí)行 promiseResolveThenableJobTask 時(shí)就相當(dāng)于執(zhí)行了 p3.then((value) => {ReslovePromise(p2, value)})
因?yàn)?p3 的狀態(tài)是 fulfilled ,所以會(huì)將其 onFulfilled 加入 microtask 隊(duì)列(value參數(shù)就是 p3 的值 4,后序他將傳遞給p2),這時(shí) microtask 隊(duì)列就變成這樣:
同樣是取 p6.onFulfilled 執(zhí)行,然后輸出 2 并將其返回值 undefined 設(shè)置為 p7 的值,并將 p7 變?yōu)?fulfilled 狀態(tài),所以 p7 的 reactions 也會(huì)加入 microtask 隊(duì)列,這時(shí) microtask 隊(duì)列就變成這樣:
[p3.onFulfilled,p7.onFulfilled]p3.onFulfilled 出隊(duì)執(zhí)行,p3.onFulfilled 是 (value) => {ReslovePromise(p2, value)}, 參數(shù) value 是 4,所以此時(shí)就執(zhí)行 ReslovePromise(p2, 4),這就相當(dāng)于調(diào)用了 p2 的 resolve。
所以此時(shí) p2 的 值變?yōu)?4, 狀態(tài)為變 fulfilled,然后將其 reactions 挨個(gè)加入 microtask 隊(duì)列,這時(shí) microtask 隊(duì)列就變成這樣:
[p7.onFulfilled,p2.onFulfilled]p7.onFulfilled 出隊(duì)列執(zhí)行,輸出 3,p8 狀態(tài)變?yōu)?fulfille,值變?yōu)?undefined,然后 p8.onFulfilled 加入隊(duì)列
[p2.onFulfilled,p8.onFulfilled]p2.onFulfilled 出隊(duì)列執(zhí)行,輸出 4,因?yàn)?p2 沒有被在此調(diào)用 then 方法,所以就沒有產(chǎn)生下一個(gè) Promise 對象,所以也就沒有后序了。
[p8.onFulfilled]題目2
Promise.resolve().then(() => {console.log(0);return {then(resolve){resolve(4)}}; }).then((res) => {console.log(res) })Promise.resolve().then(() => {console.log(1); }).then(() => {console.log(2); }).then(() => {console.log(3); }).then(() => {console.log(5); }).then(() => {console.log(6); }) // 0 1 2 4 3 5 6與上題知識(shí)點(diǎn)一致,考察的是 Promise 的值是一個(gè)包含 then 方法的對象時(shí)發(fā)生的邏輯
關(guān)鍵字:thenable、NewPromiseResolveThenableJobTask
題目3
const p1 = new Promise((resolve, reject) => {reject(0) }) console.log(1); setTimeout(() => {p1.then(undefined, console.log) }, 0) console.log(2); // 1 // 2 // 輸出報(bào)錯(cuò) UnhandledPromiseRejection: This error originated eitherconst p1 = new Promise((resolve, reject) => {reject(0) }) console.log(1); p1.then(undefined, console.log) console.log(2); // 1 // 2 // 0為什么第一種方式會(huì)報(bào)錯(cuò)?
考察的是規(guī)范中的 HostPromiseRejectionTracker,當(dāng)一個(gè)沒有綁定處理函數(shù)的 Promsie 被調(diào)用 reject 則會(huì)創(chuàng)建一個(gè) 微任務(wù)來再次檢測這個(gè) Promise 是否存在處理函數(shù),如果此時(shí)還不存在則輸出報(bào)錯(cuò),setTimeout回調(diào)執(zhí)行在微任務(wù)之后。
本文 reject > HostPromiseRejectionTracker 目錄處有詳細(xì)介紹。
注意: 瀏覽器控制臺(tái)有一個(gè)非常奇怪的特性,如果在這個(gè)錯(cuò)誤輸出后在為其綁定 onrejected 處理函數(shù),瀏覽器會(huì)將控制臺(tái)的錯(cuò)誤覆蓋掉。所以如果你在瀏覽器執(zhí)行這段代碼,請將setTimeout的時(shí)間設(shè)置長一點(diǎn),這樣效果更加容易肉眼可見。
題目四
為什么 async1 end 輸出在 promise3 之后?
題解
這個(gè)問題,這里面涉及 async 中使用 await 會(huì)產(chǎn)生多個(gè)Promise鏈路的問題以及 resolve 值為 Promise 對象的問題,重點(diǎn)看 async2,我把他轉(zhuǎn)化為這樣(setTimeout對于本題懸念不大,就刪除了)。
想必你也發(fā)現(xiàn)了 async 函數(shù)中 await 語句之前的代碼都是同步執(zhí)行的(相當(dāng)于Promsie的executor)
先調(diào)用 async1,輸出 async1 start, 同步執(zhí)行到 async2() 的位置,然后去同步執(zhí)行 async2。
然后輸出 async2,里面創(chuàng)建一個(gè) fulfilled 狀態(tài)的 p1,然后為 p1 綁定 then,因?yàn)?p1 是 fulfilled 狀態(tài)所以 p1.onFulfilled 會(huì)立即進(jìn)入 microtask 隊(duì)列。這時(shí) microtask 隊(duì)列就變成這樣:
[p1.onFulfilled]然后返回 p2(resolve(p2)),又因?yàn)?p2 是一個(gè) Promise 對象,所以創(chuàng)建一個(gè)如下的 promiseResolveThenableJobTask
let promiseResolveThenableJobTask = () => {p2.then((value) => { ReslovePromise(a2, value) // ReslovePromise 的作用上面有介紹}) } [p1.onFulfilled,promiseResolveThenableJobTask]然后執(zhí)行 await a2,這里很關(guān)鍵,await 會(huì)等待一個(gè) Promsie 進(jìn)入 fulfilled 狀態(tài)后才執(zhí)行后面的代碼,其實(shí)就相當(dāng)于下面這樣
a2.then(_ => {// 這里是 async1 中 await 后的代碼 }) // 為 a2 綁定了 reactions,這里的 onFulfill 暫時(shí)就叫 『async1后半段』吧 // 其實(shí)這里面做的事情很多,我后面可能會(huì)單獨(dú)講解 async/await 的原理注意此時(shí) a2 還不是 fulfilled 狀態(tài),因?yàn)樗枰却?promiseResolveThenableJobTask 執(zhí)行時(shí)來調(diào)用他的 resolve 才會(huì)變成 fulfilled。
這時(shí) async1() 觸發(fā)的同步代碼才執(zhí)行完畢,繼續(xù)執(zhí)行后面的 new Promise
同步執(zhí)行這段代碼
function (resolve) {console.log("promise1");resolve(); }輸出 promise1, 執(zhí)行 resolve, 然后 p3 狀態(tài)變?yōu)?fulfilled,p3.onFulfilled 進(jìn)入隊(duì)列,后面的 then 都是給對應(yīng)的 promsie 綁定 reactions 這個(gè)就不說了,最后輸出 script end
到此時(shí)所有同步代碼執(zhí)行完成, microtask 隊(duì)列是這樣的:
至此所有同步代碼執(zhí)行完成,開始取 microtask 執(zhí)行,首先是 p1.onFulfilleed ,執(zhí)行輸出 async2-inner, 然后 將其返回值 undefined 作為 p2 的值,并將 p2 變成 fulfilled 狀態(tài)。因?yàn)?p2 此時(shí)沒有 reactions (也就是沒有被調(diào)用過then方法),所以不會(huì)發(fā)生什么事情
[promiseResolveThenableJobTask,p3.onFulfilled]promiseResolveThenableJobTask 出隊(duì)列執(zhí)行, 其內(nèi)容如下,上面已經(jīng)說過了
let promiseResolveThenableJobTask = () => {p2.then((value) => { ReslovePromise(a2, value) // ReslovePromise 的作用上面有介紹}) }執(zhí)行是執(zhí)行 p2 的 then 方法為其綁定 onFulfilled 處理函數(shù),但是 p2 已經(jīng)是 fulfilled 狀態(tài),所以會(huì)直接將 p2.onFulfilled 加入 microtask 隊(duì)列。
[p3.onFulfilled, p2.onFulfilled]p3.onFulfilled 出隊(duì)執(zhí)行,輸出 promise2 ,將 p4 的狀態(tài)變?yōu)?fulfilled,p4的值為其1返回值也就是undefined,然后 p4 的 onFulfilled 也會(huì)加入 microtask 隊(duì)列
[p2.onFulfilled, p4.onFulfilled]p2.onFulfilled 出隊(duì)列執(zhí)行, p2.onFulfilled 的內(nèi)容如下,這個(gè)上面說過
(value) => { ReslovePromise(a2, value) // 也就是 a2 的 resolve(value) }所以執(zhí)行 ReslovePromise 后,a2 會(huì)變成 fulfilled 狀態(tài),a2.onFulfilled 也就是 『async1后半段』 也理所當(dāng)然的進(jìn)入 microtask 隊(duì)列
[p4.onFulfilled, async1后半段]后面的結(jié)果就沒有什么難點(diǎn)了,p4.onFulfilled 出隊(duì)執(zhí)行,輸出 promise3,然后 p5.onFulfilled 入隊(duì)
[async1后半段,p5.onFulfilled]async1后半段 出隊(duì)執(zhí)行,輸出 async1 end, 然后 a1 狀態(tài)變?yōu)?fulfilled,但是沒有綁定任何處理函數(shù),所以 a1 就沒有后續(xù)了
[p5.onFulfilled]p5.onFulfilled 出隊(duì)執(zhí)行輸出 promise4,同理 p6 沒有綁定任何處理函數(shù),至此所有代碼執(zhí)行完成
總結(jié)
以上是生活随笔為你收集整理的V8 Promise源码全面解读的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 记录webpack的source map
- 下一篇: 算法与数据结构_数据结构与算法专题--算