日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

V8 Promise源码全面解读

發布時間:2024/8/1 编程问答 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 V8 Promise源码全面解读 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

閱讀本文你將收獲什么?

了解 V8 Promise 源碼全過程,世界上不再有能困住你的 Promise 題目,我就是這么肯定這篇文章的干貨
僅僅了解或者實現了 Promise/A+ 規范,這與 JavaScript 的 Promise 中間還有很大的差距
如果你在面試時將 Promise 回答到本文的深度,一定是收獲 SP 或者 SSP offer 的利器,因為面試官大概率也不知道這些知識。

你知道 瀏覽器 & Node 中真正的 Promise 執行順序是怎么樣的嗎,如果你只是看過 Promise/A+ 規范的 Promise 實現,那么我肯定的告訴你,你對 Promise 執行順序的認知是錯誤的。不信的話你就看看下面這兩道題。

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+ 規范來說,上面的代碼打印的結果應該是 0 1 2 4 3 5 6,因為當 then 返回一個 Promise 的時候需要等待這個 Promise 完成后再同步狀態和值給 then 的結果。
但是在 V8 甚至 各大支持 Promise 的主流瀏覽器中的執行結果都是 0 1 2 3 4 5 6

他們是如何做到與 Promise/A+ 規范不一樣(也不能說一樣,因為 Promise 沒有明確描述他們的執行邏輯,只是給出一些規范)且保持一致的?

要知道, Promise 屬于 JavaScript 中的一部分, 而 JavaScript 中 Promise 的實現規范并非來源于 Promise/A+,而是來自 ECMAScript 規范。
所以要知道這個問題的答案,我們不能僅僅看 Promise/A+ ,對于代碼的執行過程和順序我們的關注點應該是在 ECMAScript 或者 V8 上。
接下來我就結合 ECMAScript 規范來對 V8 中 Promise 源碼進行全面的解讀。

PromiseState

Promise 的 3 種狀態,pending、 fulfilled 和 rejected ,源碼如下:

// Promise constants extern enum PromiseState extends int31 constexpr 'Promise::PromiseState' {kPending,// 等待狀態kFulfilled,// 成功狀態kRejected// 失敗狀態 }

一個新創建的 Promise 處于 pending 狀態。當調用 resolve 或 reject 函數后,Promise 處于 fulfilled 或 rejected 狀態,此后 Promise 的狀態保持不變,也就是說 Promise 的狀態改變是不可逆的,如果再次調用 resolve 或者 reject 將不會發生任何事,Promise 源碼中出現了多處狀態相關的 assert(斷言),這個就不贅述了,想必對于 Promise 的三種狀態大家都比較熟悉了。
JSPromise
JSPromise 描述 Promise 的基本信息,源碼如下:

bitfield struct JSPromiseFlags extends uint31 {// Promise 的狀態,kPending/kFulfilled/kRejectedstatus: PromiseState: 2 bit; // 是否有onFulfilled/onRejected處理函數,// 沒有調用過 then 方法的 Promise 沒有處理函數//(catch方法的本質是then方法,后面會介紹)has_handler: bool: 1 bit; handled_hint: bool: 1 bit; async_task_id: int32: 22 bit; }@generateCppClass extern class JSPromise extends JSObject {macro Status(): PromiseState {// 獲取 Promise 的狀態,返回 // kPending/kFulfilled/kRejected 中的一個return this.flags.status;}macro SetStatus(status: constexpr PromiseState): void {// 只有 pending 狀態的 Promise 才可以被改變狀態assert(this.Status() == PromiseState::kPending);// Promise 創建成功后,不可將 Promise 設置為 pending 狀態assert(status != PromiseState::kPending);this.flags.status = status;}macro HasHandler(): bool {// 判斷 Promise 是否有處理函數return this.flags.has_handler;}macro SetHasHandler(): void {this.flags.has_handler = true;}// promise 處理函數或結果,可以是:// 空// onFulfilled/onRejected構成的鏈表// promise的確認值(resolve的參數)reactions_or_result: Zero|PromiseReaction|JSAny;flags: SmiTagged<JSPromiseFlags>; }

當 Promise 狀態改變時,比如調用了 resolve/reject 函數,SetStatus 方法會被調用;Javascript 層調用 resolve 方法時,reactions_or_result 字段會被賦值為 resolve 傳入的參數;Javascript 層調用 then 方法時,說明已經有了處理函數,SetHasHandler() 會被調用。Status/SetStatus 這兩個方法一個獲取 Promise 狀態,一個設置 Promise 狀態;
其它

executor:是一個函數,Promise 構造函數接收的參數,調用 executor 時傳入的參數分別是 resolve 和 reject。
PromiseReaction:是對象,表示 Promise 的處理函數,因為一個 Promise 多次調用 then 方法就會有多個處理函數,所以底層數據結構是個鏈表,每一個節點都存儲著 onFulfilled 和 onRejected 函數。

let p = new Promise((resolve, reject) => {resolve(123)// 會將 reactions_or_result 設置為 123// 會調用 SetHasHandlerresolve(234)// 不會發生任何事,相當于沒寫reject(234)// 也不會發生任何事,相當于沒寫 })

復制代碼
構造函數
構造函數源碼如下:

PromiseConstructor(js-implicit context: NativeContext, receiver: JSAny,newTarget: JSAny)(executor: JSAny): JSAny {// 1. 如果不存在 new 關鍵字, throw a TypeError exception.if (newTarget == Undefined) {ThrowTypeError(MessageTemplate::kNotAPromise, newTarget);}// 2. 如果傳入的參數不是一個回調函數, throw a TypeError exception.if (!Is<Callable>(executor)) {ThrowTypeError(MessageTemplate::kResolverNotAFunction, executor);}let result: JSPromise;// 構造一個 Promise 對象result = NewJSPromise();// 從 Promise 對象身上,獲取它的 resolve 和 reject 函數const funcs = CreatePromiseResolvingFunctions(result, True, context);const resolve = funcs.resolve;const reject = funcs.reject;try {// 直接同步調用 executor 函數,resolve 和 reject 做為參數Call(context, UnsafeCast<Callable>(executor), Undefined, resolve, reject);} catch (e) {// 如果出現異常則調用 reject 函數Call(context, reject, Undefined, e);}return result; }

首先分析兩個 ThrowTypeError,以下代碼可觸發第一個 ThrowTypeError。

Promise() // Uncaught TypeError: undefined is not a promise

原因是沒有使用 new 操作符調用 Promise 構造函數,此時 newTarget 等于 Undefined,觸發了 ThrowTypeError(MessageTemplate::kNotAPromise, newTarget)。
以下代碼可觸發第二個 ThrowTypeError。

new Promise() // Uncaught TypeError: Promise resolver undefined is not a function

此時 newTarget 不等于 Undefined,不會觸發第一個 ThrowTypeError。但調用 Promise 構造函數時沒傳參數 executor,觸發了第二個 ThrowTypeError。
executor 的類型是函數,在 JavaScript 的世界里,回調函數通常是異步調用,但 executor 是同步調用。在 Call(context, UnsafeCast(executor), Undefined, resolve, reject) 這一行,同步調用了 executor。

console.log('同步執行開始') new Promise((resolve, reject) => {resolve()console.log('executor 同步執行') })console.log('同步執行結束') // 本段代碼的打印順序是: // 同步執行開始 // executor 同步執行 // 同步執行結束

Promise 構造函數接收的參數 executor,是被同步調用的

then

ECMAScript 規范
PromisePrototypeThen
Promise 的 then 方法傳入兩個回調函數 onFulfilled 和 onRejected,分別用于處理 fulfilled 和 rejected 狀態,并返回一個新的 Promise。
JavaScript 層的 then 函數實際上是 V8 中的 PromisePrototypeThen 函數,源碼如下:

PromisePrototypeThen(js-implicit context: NativeContext, receiver: JSAny)(onFulfilled: JSAny, onRejected: JSAny): JSAny {const promise = Cast<JSPromise>(receiver) otherwise ThrowTypeError(MessageTemplate::kIncompatibleMethodReceiver, 'Promise.prototype.then',receiver);const promiseFun = UnsafeCast<JSFunction>(context[NativeContextSlot::PROMISE_FUNCTION_INDEX]);let resultPromiseOrCapability: JSPromise|PromiseCapability;let resultPromise: JSAny;label AllocateAndInit {// 創建一個新的 promise 用于當做本次 then 的調用結果返回//(上面有提到then的返回值是一個promise)const resultJSPromise = NewJSPromise(promise);resultPromiseOrCapability = resultJSPromise;resultPromise = resultJSPromise;}// onFulfilled 和 onRejected 是 then 接收的兩個參數// 如果不傳則默認值為 Undefinedconst onFulfilled = CastOrDefault<Callable>(onFulfilled, Undefined);const onRejected = CastOrDefault<Callable>(onRejected, Undefined);// 調用 PerformPromiseThenImpl 函數PerformPromiseThenImpl(promise, onFulfilled, onRejected, resultPromiseOrCapability);// 返回一個新的 Promisereturn resultPromise; }

PromisePrototypeThen 函數創建了一個新的 Promise 對象,獲取 then 接收到的兩個參數,調用 PerformPromiseThenImpl 完成大部分工作。這里有一點值得注意,then 方法返回的是一個新創建的 Promise。

const myPromise2 = new Promise((resolve, reject) => {resolve('foo') })const myPromise3 = myPromise2.then(console.log)// myPromise2 和 myPromise3 是兩個不同的對象 // 有不同的狀態和不同的處理函數 console.log(myPromise2 === myPromise3) // 打印 false

then 方法返回的是一個新的 Promise

PerformPromiseThenImpl

ECMAScript 規范
PerformPromiseThenImpl 有4個參數,因為 PerformPromiseThenImpl 是在調用 then 調用,所以它的前三個參數分別是被調用 then 方法的 Promise 對象,以及這個對象的兩個處理函數 onFulfilled 和 onRejected,最后一個參數為調用這個 then 返回的新 Promise 對象 resultPromiseOrCapability。
PerformPromiseThenImpl 源碼如下:

transitioning macro PerformPromiseThenImpl(implicit context: Context)(promise: JSPromise, onFulfilled: Callable|Undefined,onRejected: Callable|Undefined,resultPromiseOrCapability: JSPromise|PromiseCapability|Undefined): void {if (promise.Status() == PromiseState::kPending) {// pending 狀態的分支// 如果當前 Promise 還是 pending 狀態// 那么只需要將本次 then 綁定的處理函數存儲起來即可const handlerContext = ExtractHandlerContext(onFulfilled, onRejected);// 拿到 Promise 的 reactions_or_result 字段const promiseReactions =UnsafeCast<(Zero | PromiseReaction)>(promise.reactions_or_result);// 考慮一個 Promise 可能會有多個 then 的情況// reaction 是個鏈表,每次綁定處理函數都在鏈表的頭部插入// 存 Promise 的所有處理函數const reaction = NewPromiseReaction(handlerContext, promiseReactions, resultPromiseOrCapability,onFulfilled, onRejected);// reactions_or_result 可以存 Promise 的處理函數的鏈表,也可以存// Promise 的最終結果,因為現在 Promise 處于 pending 狀態,// 所以存的是處理函數 reaction 構成的鏈表promise.reactions_or_result = reaction;} else {// fulfilled 和 rejected 狀態的分支const reactionsOrResult = promise.reactions_or_result;let microtask: PromiseReactionJobTask;let handlerContext: Context;// fulfilled 分支if (promise.Status() == PromiseState::kFulfilled) {handlerContext = ExtractHandlerContext(onFulfilled, onRejected);// 生成 microtask 任務microtask = NewPromiseFulfillReactionJobTask(handlerContext, reactionsOrResult, onFulfilled,resultPromiseOrCapability);} else // rejected 分支deferred {assert(promise.Status() == PromiseState::kRejected);handlerContext = ExtractHandlerContext(onRejected, onFulfilled);// 生成 microtask 任務microtask = NewPromiseRejectReactionJobTask(handlerContext, reactionsOrResult, onRejected,resultPromiseOrCapability);// 如果當前 promise 還未綁定過處理函數if (!promise.HasHandler()) {// 規范中的 HostPromiseRejectionTracker(promise, "reject"),// 作用是產生一個檢測的 microtask 任務,后面會單獨介紹。runtime::PromiseRevokeReject(promise);}}// 即使調用 then 方法時 promise 已經處于 fulfilled 或 rejected 狀態,// then 方法的 onFulfilled 或 onRejected 參數也不會立刻執行,// 而是進入 microtask 隊列后執行EnqueueMicrotask(handlerContext, microtask);}promise.SetHasHandler(); }

PerformPromiseThenImpl 函數的 pending 分支
PerformPromiseThenImpl 有三個分支,分別對應 Promise 的三個狀態,當被調用 then 方法的 Promise 處于 pending 狀態時則進入 pending分支。pending 分支調用 NewPromiseReaction 函數,在接收到的 onFulfilled 和 onRejected 參數的基礎上,生成 PromiseReaction 對象,存儲 Promise 的處理函數,并賦值給 JSPromise 的 reactions_or_result 字段,然后調用 promise.SetHasHandler() 將 has_handler 設置為 true(表示這個 Promise 對象已經綁定了處理函數)
考慮一個 Promise 可以會連續調用多個 then 的情況,比如:

const p = new Promise((resolve, reject) => {setTimeout(_ => {resolve('my code delay 2000 ms') }, 2000) })p.then(result => {console.log('第 1 個 then') })p.then(result => {console.log('第 2 個 then') })

p 調用了兩次 then 方法,每個 then 方法都會生成一個 PromiseReaction 對象。第一次調用 then 方法時生成對象 PromiseReaction1,此時 p 的 reactions_or_result 存的是 PromiseReaction1。
第二次調用 then 方法時生成對象 PromiseReaction2,調用 NewPromiseReaction 函數時,PromiseReaction2.next = PromiseReaction1,PromiseReaction1 變成了 PromiseReaction2 的下一個節點,最后 p 的 reactions_or_result 存的是 PromiseReaction2。PromiseReaction2 后進入 Promise 處理函數的鏈表,卻是鏈表的頭結點。NewPromiseReaction 函數源碼如下:

macro NewPromiseReaction(implicit context: Context)(handlerContext: Context, next: Zero|PromiseReaction,promiseOrCapability: JSPromise|PromiseCapability|Undefined,fulfillHandler: Callable|Undefined,rejectHandler: Callable|Undefined): PromiseReaction {const nativeContext = LoadNativeContext(handlerContext);return new PromiseReaction{map: PromiseReactionMapConstant(),next: next, // next 字段存的是鏈表中的下一個節點reject_handler: rejectHandler,// 失敗處理函數fulfill_handler: fulfillHandler,// 成功處理函數promise_or_capability: promiseOrCapability,// 產生的新Promise對象continuation_preserved_embedder_data: nativeContext[NativeContextSlot::CONTINUATION_PRESERVED_EMBEDDER_DATA_INDEX]}; }

在 p 處于 pending 狀態時,p 的 reactions_or_result 字段大致內容如下圖。

下圖不是 microtask 隊列,下圖不是 microtask 隊列,下圖不是 microtask 隊列。

圖中使用 onFulfilled 代替 fulfill_handler 是為了方便理解,onRejected也是如此,且只包含于當前內容相關的字段,不用太過于糾結。

PerformPromiseThenImpl 函數的 fulfilled 分支
fulfilled 分支邏輯則簡單的多,處理的是當 Promise 處于 fulfilled 狀態時,調用 then 方法的邏輯:
先調用 NewPromiseFulfillReactionJobTask 生成 microtask,然后 EnqueueMicrotask(handlerContext, microtask) 將剛才生成的 microtask 放入 microtask 隊列,最后調用 promise.SetHasHandler() 將 has_handler 設置為 true。

new Promise((resolve, reject) => {resolve() }).then(result => {console.log('進入 microtask 隊列后執行') })console.log('同步執行結束') // 本段代碼的打印順序是: // 同步執行結束 // 進入 microtask 隊列后執行

盡管調用 then 方法時,Promise 已經處于 fulfilled 狀態,但 then 方法的 onFulfilled 回調函數不會立即執行,而是進入 microtask 隊列等待執行。
PerformPromiseThenImpl 函數的 rejected 分支
rejected 分支邏輯與 fulfilled 分支的邏輯大致相同,但是 rejected 分支中將 onRejected 處理函數加入 microtask 隊列之前,會先判斷當前 promise 是否已經存在處理函數,如果已經存在則會先調用
runtime::PromiseRevokeReject(promise),最后調用 promise.SetHasHandler() 將 has_handler 設置為 true。

if (!promise.HasHandler()) {runtime::PromiseRevokeReject(promise);}

這里的runtime::PromiseRevokeReject(promise) 就是 ECMAScript 規范 中的 HostPromiseRejectionTracker(promise, “handle”),HostPromiseRejectionTracker 是一個抽象方法,這表示沒有規定它的具體的邏輯。大致的作用是標記一下 promise 已經綁定了 rejected 狀態的處理函數。不用疑惑為什么要這么做,后面會單獨重點說。

注意 1 HostPromiseRejectionTracker 在兩種情況下被調用:
當一個 promise 在沒有任何處理程序的情況下被拒絕時,它的操作參數設置為“reject”。
當第一次將處理程序添加到被拒絕的 Promise 中時,將調用它并將其操作參數設置為“handle”。
引至 ——ECMAScript 規范(作者翻譯)

小結

當一個 Promise 被調用 then 方法時,會創建一個新的 Promise 對象 resultPromise

然后根據當前 promise 的不同狀態執行不同的邏輯

pending狀態:會將 then 傳遞的兩個處理函數變成一個 PromiseReaction 節點插入到promise.reactions_or_result 頭部(PromiseReaction是一個鏈表結構),這個步驟就是在搜集依賴,等待 promise 狀態完成時再觸發。
fulfilled狀態:會創建一個 microtask 來調用傳入的 onFulfilled 處理函數,并將 reactions_or_result 作為調用的參數(此時 reactions_or_result 是 promise 的值,也就是調用 resolve 時傳入的參數 value ),并將其插入 microtask 隊列。
rejected狀態:與 fulfilled 狀態類似,會將創建一個 microtask 來調用傳入的 onRejected 處理函數,并將 reactions_or_result 作為調用的參數,如果當前 Promise 不存在處理函數(也就是 fulfilled 狀態的Promsie 首次被調用 then 方法),會將其標記為已經綁定 onRejected 函數,然后將其 microtask 插入 microtask 隊列。

調用 promise.SetHasHandler() 將 Promise 的 has_handler 設置為 true,表示其被調用的 then 方法綁定了處理函數。

最后返回新的 Promise 對象。

再來回顧一下 reactions_or_result 的3個值狀態(空、鏈表、promise的值):
當 promise 剛剛被創建時,reactions_or_result的值的空,
當promise的狀態改變為 fulfilled/rejected 時,其值是調用對應 resolve(value)/reject(value) 函數傳入的參數 value,也就是 promise 的值。
當 promise 為 pending 狀態且被調用 then 后,reactions_or_result 為一個鏈表,鏈表的每一項存儲的是調用 then 時傳入的處理函數。

reslove new Promise((resolve, reject) => {setTimeout(_ => resolve('fulfilled'), 5000) }).then(value => {console.log(value) }, reason => {console.log('rejected') })

上述代碼 5s 后執行 resolve 函數,控制臺打印 fulfilled。
FulfillPromise
ECMAScript 規范
reslove(value) 就是規范中的 FulfillPromise(promise, value) 函數,他的作用是將一個 Promise 的狀態由 pending 改變為 fulfilled,并且將這個 Promise 的所有處理函數都變成 microtask 加入到 microtask 隊列中等待執行。
resolve 函數歸根到底調用了 V8 的 FulfillPromise 函數,源碼如下:

// https://tc39.es/ecma262/#sec-fulfillpromise transitioning builtin FulfillPromise(implicit context: Context)(promise: JSPromise, value: JSAny): Undefined {// 斷案當前promise狀態一定是 pending,因為promise 的狀態改變是不可逆的assert(promise.Status() == PromiseState::kPending);// 取 Promise 的處理函數,在這之前 Promise 的狀態還是 pending// 所以 reactions_or_result 中存的是 reactions 鏈表,// reactions 節點中存儲的是里函數const reactions =UnsafeCast<(Zero | PromiseReaction)>(promise.reactions_or_result);// Promise 需要修改為 fulfilled 狀態,所以 reactions_or_result 存儲的// 不再是處理函數,而是 Promise 的結果,也就是調用 resolve 時傳入的參數promise.reactions_or_result = value;// 設置 Promise 的狀態為 fulfilledpromise.SetStatus(PromiseState::kFulfilled);// Promise 的處理函數,Promise 的結果都拿到了,開始正式處理TriggerPromiseReactions(reactions, value, kPromiseReactionFulfill);return Undefined; }

FulfillPromise 的邏輯是獲取 Promise 的處理函數到 reactions,reactions 的類型是 PromiseReaction,是個鏈表,忘記的同學可以回看上面的那張鏈表圖片;設置 promise 的 reactions_or_result 為 value,這個 value 就是 JavaScript 層傳給 resolve 的參數;調用 promise.SetStatus(PromiseState::kFulfilled) 設置 promise 的狀態為 fulfilled,最后調用 TriggerPromiseReactions 來將 reactions 中的處理函數添加到 microtask 隊列。
TriggerPromiseReactions
源碼如下:

// https://tc39.es/ecma262/#sec-triggerpromisereactions transitioning macro TriggerPromiseReactions(implicit context: Context)(reactions: Zero|PromiseReaction, argument: JSAny,reactionType: constexpr PromiseReactionType): void {// We need to reverse the {reactions} here, since we record them on the// JSPromise in the reverse order.let current = reactions;let reversed: Zero|PromiseReaction = kZero;// 鏈表反轉while (true) {typeswitch (current) {case (Zero): {break;}case (currentReaction: PromiseReaction): {current = currentReaction.next;currentReaction.next = reversed;reversed = currentReaction;}}}current = reversed;// 鏈表反轉后,調用 MorphAndEnqueuePromiseReaction// 把鏈接中的每一項都進入 microtask 隊列while (true) {typeswitch (current) {case (Zero): {break;}case (currentReaction: PromiseReaction): {current = currentReaction.next;MorphAndEnqueuePromiseReaction(currentReaction, argument, reactionType);}}} }

TriggerPromiseReactions 做了兩件事:

反轉 reactions 鏈表,前文有分析過 then 方法的實現,then 方法的參數最終存在鏈表中。最后被調用的 then 方法,它接收的參數被包裝后會位于鏈表的頭部,這不符合規范,所以需要反轉
遍歷 reactions 對象,調用 MorphAndEnqueuePromiseReaction 將每個元素放入 microtask 隊列

MorphAndEnqueuePromiseReaction
MorphAndEnqueuePromiseReaction 將 PromiseReaction 轉為 microtask,最終插入 microtask 隊列,morph 本身有轉變/轉化的意思,比如 Polymorphism (多態)。
MorphAndEnqueuePromiseReaction 接收 3 個參數,PromiseReaction 是前面提到的包裝了 Promise 處理函數的鏈表對象,argument 是 resolve/reject 的參數,reactionType 表示 Promise 最終的狀態,fulfilled 狀態對應的值是 kPromiseReactionFulfill,rejected 狀態對應的值是 kPromiseReactionReject。
MorphAndEnqueuePromiseReaction 的邏輯很簡單,因為此時已經知道了 Promise 的最終狀態,所以可以從 promiseReaction 對象得到 promiseReactionJobTask 對象,promiseReactionJobTask 的變量命名與 ECMAScript 規范相關描述一脈相承,其實就是傳說中的 microtask。MorphAndEnqueuePromiseReaction 源碼如下,僅保留了和本小節相關的內容。

transitioning macro MorphAndEnqueuePromiseReaction(implicit context: Context)(promiseReaction: PromiseReaction, argument: JSAny,reactionType: constexpr PromiseReactionType): void {let primaryHandler: Callable|Undefined;let secondaryHandler: Callable|Undefined;// 根據不同的 Promise 狀態選取不同的回調執行if constexpr (reactionType == kPromiseReactionFulfill) {primaryHandler = promiseReaction.fulfill_handler;secondaryHandler = promiseReaction.reject_handler;} else {primaryHandler = promiseReaction.reject_handler;secondaryHandler = promiseReaction.fulfill_handler;}const handlerContext: Context =ExtractHandlerContext(primaryHandler, secondaryHandler);if constexpr (reactionType == kPromiseReactionFulfill) {// fulfilled 分支* UnsafeConstCast(& promiseReaction.map) =PromiseFulfillReactionJobTaskMapConstant();const promiseReactionJobTask =UnsafeCast<PromiseFulfillReactionJobTask>(promiseReaction);// argument 是 reject 的參數promiseReactionJobTask.argument = argument;// handler 是 JS 層面 then 方法的第二個參數,或 catch 方法的參數promiseReactionJobTask.context = handlerContext;// promiseReactionJobTask 就是那個工作中經常被反復提起的 microtask// EnqueueMicrotask 將 microtask 插入 microtask 隊列EnqueueMicrotask(handlerContext, promiseReactionJobTask);// 刪除} else {// rejected 分支// 邏輯與 fulfilled 分支前面一致* UnsafeConstCast(& promiseReaction.map) =PromiseRejectReactionJobTaskMapConstant();const promiseReactionJobTask =UnsafeCast<PromiseRejectReactionJobTask>(promiseReaction);promiseReactionJobTask.argument = argument;promiseReactionJobTask.context = handlerContext;promiseReactionJobTask.handler = primaryHandler;EnqueueMicrotask(handlerContext, promiseReactionJobTask);} }

MorphAndEnqueuePromiseReaction 的功能很簡單,就是根據 Promise 的狀態選取 onFulfilled 還是 onRejected 放到 microtask 隊列準備執行。這里走的是 fulfilled 分支,所以選取的是 onFulfilled。

const myPromise4 = new Promise((resolve, reject) => {setTimeout(_ => {resolve('my code delay 1000') }, 1000) })myPromise4.then(result => {console.log('第 1 個 then') })myPromise4.then(result => {console.log('第 2 個 then') }) // 打印順序: // 第 1 個 then // 第 2 個 then // 如果把 TriggerPromiseReactions 中鏈表反轉的代碼注釋掉,打印順序為 // 第 2 個 then // 第 1 個 then

小結
resolve 只會處理狀態為 pending 的 Promise,會將 Promise 的 reactions_or_result 設置為傳入的 value,用來作為 Promise 的值,并且會將 Promise 的狀態修改為 fulfilled。
因為 promise 在使用 then 收集依賴時是將最新的依賴存放到鏈表頭部,所以還需要先對鏈表進行反轉,然后將其挨個放入 microtask 隊列中等待執行

resolve 的主要工作是遍歷上節調用 then 方法時收集到的依賴,放入 microtask 隊列中等待執行。

reject reject 與 reslove 沒什么太大差別 ECMAScript 規范 new Promise((resolve, reject) => {setTimeout(_ => reject('rejected'), 5000) }).then(_ => {console.log('fulfilled') }, reason => {console.log(reason) })

上述代碼 5s 后執行 reject 函數,控制臺打印 rejected。
RejectPromise
ECMAScript 規范
reject(season) 函數調用了 V8 的 RejectPromise(promise, season) 函數,源碼如下 :

// https://tc39.es/ecma262/#sec-rejectpromise transitioning builtin RejectPromise(implicit context: Context)(promise: JSPromise, reason: JSAny, debugEvent: Boolean): JSAny {// 如果當前 Promise 沒有綁定處理函數,// 則會調用 runtime::RejectPromiseif (IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate() ||!promise.HasHandler()) {return runtime::RejectPromise(promise, reason, debugEvent);}// 取出 Promise 的處理對象 PromiseReactionconst reactions =UnsafeCast<(Zero | PromiseReaction)>(promise.reactions_or_result);// 這里的 reason 就是 reject 函數的參數promise.reactions_or_result = reason;// 設置 Promise 的狀態為 rejectedpromise.SetStatus(PromiseState::kRejected);// 將 Promise 的處理函數都添加到 microtask 隊列TriggerPromiseReactions(reactions, reason, kPromiseReactionReject);return Undefined; }

HostPromiseRejectionTracker

與 ReslovePromise 相比,RejectPromise 中多出一個判斷 Promsie 是否綁定了處理函數的判斷,如果沒有綁定處理函數則會先執行 runtime::RejectPromise(promise, reason, debugEvent),這是其實是 ECMAScript 規范中的 HostPromiseRejectionTracker(promise, “reject”) ,這已經是第二次提到 HostPromiseRejectionTracker了。

在 PerformPromiseThenImpl 函數的 rejected 分支 處有提到過一次。

在 ECMAScript 規范中 HostPromiseRejectionTracker 是一個抽象方法,他甚至沒有明確的執行過程,好在規范中描述了他的作用。
HostPromiseRejectionTracker 用于跟蹤 Promise 的 rejected,例如全局的 rejectionHandled 事件就是由它實現。

注1
HostPromiseRejectionTracker 在兩種情況下被調用:
當一個 Promise 在沒有任何處理函數的情況下被調用 reject 時,調用它并且第二個參數傳遞 “reject”。
當第一次為 rejected 狀態的 Promise 綁定處理函數時,調用它并且第二個參數傳遞 “handle”。

所以在這里,當傳遞 “handle” 就相對于為這個 Promise 對象標記為已經綁定了處理函數,當傳遞 “reject” 相對于標記這個 Promise 對象還沒有處理函數。
我們先來看幾段代碼看看他的實際作用
當我們調用一個 Promise 的狀態為 reject 且未為其綁定 onRejected 的處理函數時, JavaScript會拋出錯誤

const myPromise1 = new Promise((resolve, reject) => {reject() }) // 報錯

并且檢測是否綁定處理函數是一個異步的過程

console.log(1); const myPromise1 = new Promise((resolve, reject) => {reject() }) console.log(2); // 1 // 2 // 報錯

我們可以為其綁定一個 onRejected 處理函數來解決我們報錯

const myPromise1 = new Promise((resolve, reject) => {reject() })// 得到一個 rejected 狀態的 Promise myPromise1.then(undefined, console.log)

你一定會疑惑,Promise 是在何時檢測它是否綁定了 onRejected 處理函數,如何檢測的?
這就是 HostPromiseRejectionTracker 的作用,在 ECMAScript 規范中還提到,當調用 HostPromiseRejectionTracker(promise, ‘reject’) 時, 如果 promsie 不存在處理函數,則會為其設置一個處理函數。
回到上面的邏輯,當一個 Promise 的 reject 函數被調用時, 如果沒有 onRejected 處理函數,則會調用 runtime::RejectPromise 來為其添加一個處理函數,然后后面會調用 TriggerPromiseReactions 將這個處理函數加入到 microtask 隊列,這個處理函數時做的事情就是再次檢測 Promise 是否被綁定了新的 onRejected(也就是有沒有在此期間執行了 HostPromiseRejectionTracker(promise, ‘handle’) ),如果沒有則拋出錯誤,如果有則什么也不發生。
所以在對一個 reject 狀態的 Promise 調用 then 方法時需要對其調用 runtime::PromiseRevokeReject(promise) 來表示這個 Promise 綁定了新的 onRejected,防止錯誤被拋出。
所以你必須要趕在這個檢測的 microtask 執行之前綁定處理函數才能防止這個錯誤的拋出。

const myPromise1 = new Promise((resolve, reject) => {// 同步執行reject()// 會向 microtask 隊列中插入一個檢查 myPromise1 // 是否綁定了新的 onRejected 處理函數的 microtask })// macrotask setTimeout(() => {// 此時 microtask 已經執行,錯誤已經拋出,來不及了myPromise1.then(undefined, console.log) }, 0)

注意: 瀏覽器控制臺有一個非常奇怪的特性,如果在這個錯誤輸出后在為其綁定 onrejected 處理函數,瀏覽器會將控制臺的錯誤覆蓋掉。所以如果你在瀏覽器執行這段代碼,請將setTimeout的時間設置長一點,這樣效果更加容易肉眼可見,或者切換到 node 環境中來運行。

小結

reject 和 resolve 的邏輯基本相同,分為 4 步:

設置 Promise 的 reason,也就是 reject 的參數
設置 Promise 的狀態:rejected
如果 Promise 沒有 onRejected 處理函數,則會為其添加一個再次檢測 Promise 是否綁定 onRejected 的處理函數
從之前調用 then/catch 方法時收集到的依賴,也就是 promiseReaction 對象,得到一個個 microtask,最后將 microtask 插入 microtask 隊列

catch new Promise((resolve, reject) => {setTimeout(reject, 2000) }).catch(_ => {console.log('rejected') })

PromisePrototypeCatch

以上面代碼為例,當 catch 方法執行時,調用了 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 的源碼確實只有就這幾行,除了調用 InvokeThen 方法再無其它 。

InvokeThen

從名字可以推測出,InvokeThen 調用的是 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]);// 重點在下面一行,調用 then 方法并返回,兩個分支都一樣return callFunctor.Call(nativeContext, then, receiver, arg1, arg2);} elsedeferred {const then = UnsafeCast<JSAny>(GetProperty(receiver, kThenString));// 重點在下面一行,調用 then 方法并返回,兩個分支都一樣return callFunctor.Call(nativeContext, then, receiver, arg1, arg2);} }

InvokeThen 方法有 if/else 兩個分支,兩個分支的邏輯差不多,本小節的 JS 示例代碼走的是 if 分支。先是拿到 V8 原生的 then 方法,然后通過 callFunctor.Call(nativeContext, then, receiver, arg1, arg2) 調用 then 方法。then 方法之前有介紹,這里不再贅述。
既然 catch 方法底層調用了 then 方法,那么 catch 方法也有和 then 方法一樣的返回值,catch 方法可以繼續拋出異常,可以繼續鏈式調用。

new Promise((resolve, reject) => {setTimeout(reject, 2000) }).catch(_ => {throw 'rejected' }).catch(_ => {console.log('last catch') })

上面的代碼第 2 個 catch 捕獲第 1 個 catch 拋出的異常,最后打印 last catch。

小結

catch 方法通過底層調用 then 方法來實現
假如 obj 是一個 Promise 對象,JS 層面 obj.catch(onRejected) 等價于 obj.then(undefined, onRejected)
then 的鏈式調用與 microtask 隊列

Promise.resolve('123').then(() => {throw new Error('456')}).then(_ => {console.log('shouldnot be here')}).catch((e) => console.log(e)).then((data) => console.log(data));

以上代碼運行后,打印 Error: 456 和 undefined。為了便于敘述,將 then 的鏈式調用寫法改為啰嗦寫法。

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 個 Promise 互不相等。

當一個 Promise 處于 rejected 狀態時,如果找不到 onRejected 處理函數則會將 rejected 的狀態和其值往下傳遞,直到找到為止。(resolve也是一樣),這個過程后面會介紹
catch 方法的作用就是綁定 onRejected 函數

microtask 的執行
所有同步代碼執行完畢,開始執行取 microtask 隊列中的 microtask 執行,核心方法是 MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask ,由于 microtask 的類型由很多種,所以 RunSingleMicrotask 的分支有許多。這里就不列出代碼了。
PromiseReactionJob
在執行 microtask 的過程中,MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask 會調用 PromiseReactionJob,源碼如下:

transitioning macro PromiseReactionJob(context: Context, argument: JSAny, handler: Callable|Undefined,promiseOrCapability: JSPromise|PromiseCapability|Undefined,reactionType: constexpr PromiseReactionType): JSAny {if (handler == Undefined) {// 沒有處理函數的 case,透傳上一個 Promise 的 argumentif constexpr (reactionType == kPromiseReactionFulfill) {// 基本類同 JS 層的 resolvereturn FuflfillPromiseReactionJob(context, promiseOrCapability, argument, reactionType);} else {// 基本類同 JS 層的 rejectreturn RejectPromiseReactionJob(context, promiseOrCapability, argument, reactionType);}} else {try {// 試圖調用 Promise 處理函數,相當于 handler(argument)const result =Call(context, UnsafeCast<Callable>(handler), Undefined, argument);// 基本類同 JS 層的 resolvereturn FuflfillPromiseReactionJob(context, promiseOrCapability, result, reactionType);} catch (e) {// 基本類同 JS 層的 reject,當執行 handler 是拋出異常會觸發return RejectPromiseReactionJob(context, promiseOrCapability, e, reactionType);}} }

PromiseReactionJob 中會判斷當前任務是否存在需要執行的處理函數,如果不存在則直接將上一個 Promise 的值作為參數調用 FuflfillPromiseReactionJob ,如果存在則執行這個處理函數,將執行結果當做參數調用 FuflfillPromiseReactionJob。
也就是說,只要一個 Promise 的 onFulfilled 或者 onRejected 在執行過程中只要沒有拋出異常,這個 Promise 就會執行 FuflfillPromiseReactionJob 將狀態修改為 fulfilled。如果拋出異常則執行 RejectPromiseReactionJob。

let p0 = new Promise((resolve, reject) => {reject(123) }) // p1 的狀態為 rejectlet p1 = p0.then(value => {console.log(value); }, reason => {console.log(reason);return 2 }) // 將 reason => {console.log(reason)} 加入 microtask 隊列p1.then(_ => {console.log('p1'); }) // 為 p1 添加 PromiseReaction// 取 microtask 隊列 第一個執行, // handler 為 reason => {console.log(reason)},// 成功執行 handler, 所以調用 FuflfillPromiseReactionJob // 執行 p1 的 resolve

注意:FuflfillPromiseReactionJob 做的事情很多,執行 resolve 只是其中的一個分支

我們來看看 FuflfillPromiseReactionJob 具體做了哪些事情。
FuflfillPromiseReactionJob
源碼如下:

transitioning macro FuflfillPromiseReactionJob(context: Context,promiseOrCapability: JSPromise|PromiseCapability|Undefined, result: JSAny,reactionType: constexpr PromiseReactionType): JSAny {typeswitch (promiseOrCapability) {case (promise: JSPromise): {// 調用 ResolvePromise,也就是 promise 的 resolve(result)return ResolvePromise(context, promise, result);}case (Undefined): {return Undefined;}case (capability: PromiseCapability): {const resolve = UnsafeCast<Callable>(capability.resolve);try {return Call(context, resolve, Undefined, result);} catch (e) {return RejectPromiseReactionJob(context, promiseOrCapability, e, reactionType);}}} }

FuflfillPromiseReactionJob 有3個分支,這里走的是第一個分支,調用 ResolvePromise,這個方法很重要,他是規范中的 Promise Resolve Functions,他的作用是同步當前處理函數的結果(值和狀態)給其產生的promsie。promiseOrCapability。

上面例子中 promiseOrCapability 就是 p1, 值是 2

ResolvePromise
這是一個很重要的方法,基本上每一個 Promise 的狀態需要變成 fulfilled 都會調用它,它的邏輯也產生了許多 PromiseA+ 中沒有的特性。 下面的代碼我刪除了不重要的部分

// https://tc39.es/ecma262/#sec-promise-resolve-functions transitioning builtin ResolvePromise(implicit context: Context)(promise: JSPromise, resolution: JSAny): JSAny {// 刪let then: Object = Undefined;try {// 調用 FulfillPromiseconst heapResolution = UnsafeCast<HeapObject>(resolution);const resolutionMap = heapResolution.map;if (!IsJSReceiverMap(resolutionMap)) {return FulfillPromise(promise, resolution);}// 刪const promisePrototype =*NativeContextSlot(ContextSlot::PROMISE_PROTOTYPE_INDEX);// 重要:如果 resolution 是一個 Promise 對象if (resolutionMap.prototype == promisePrototype) {then = *NativeContextSlot(ContextSlot::PROMISE_THEN_INDEX);static_assert(nativeContext == LoadNativeContext(context));goto Enqueue;}goto Slow;} label Slow deferred {// 如果 resolution 是一個包含then屬性的對象,會來到這try {// 獲取 then 屬性then = GetProperty(resolution, kThenString);} catch (e) {return RejectPromise(promise, e, False);}// 如果 then 屬性不是一個可執行的方法if (!Is<Callable>(then)) {// 將執行結果同步到 promisereturn FulfillPromise(promise, resolution);}goto Enqueue;} label Enqueue {// 重要:如果 執行結果是一個 Promise 對象// 或者包含可執行的 then 方法的對象,會來到這const task = NewPromiseResolveThenableJobTask(promise, UnsafeCast<JSReceiver>(resolution),UnsafeCast<Callable>(then));return EnqueueMicrotask(task.context, task);} }

ResolvePromise 方法中有幾個很重要的邏輯,一個是調用 FulfillPromise,這個在resolve的時候已經介紹過了,作用是修改 promise 的狀態為 fulfilled 并為其設置值,然后將 promise 的處理函數推到微任務隊列。

let p0 = Promise.resolve() let p1 = p0.then(() => {return 1; })p1.then(console.log)// p0 then 中 onFulfilled 回調進入隊列 // PromiseReactionJob 中調用 p0 的 onFulfilled ,得到結果為1 // 調用 FuflfillPromiseReactionJob ,然后調用 ResolvePromise // ResolvePromise 做如下操作 // 將 p1 變成 fulfilled, 并將 p1 的處理函數 console.log 加到隊列,參數為 1 // p1 的 onFulfilled 出隊列執行,輸出 1 復制代碼 還有一種情況就是 當 resolution 的值是一個 Promise 對象或者是一個包含 then 方法的對象時。會調用 NewPromiseResolveThenableJobTask 生成一個 microtask,然后將其加入 microtask 隊列中。 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 的目的是調用 resolution 的 then 方法,在回調函數中同步狀態給 promise。 這可能不是很好理解,我把他轉化為js來大致就是這樣的。

microtask(() => {resolution.then((value) => {ReslovePromise(promise, value) }) })

這個任務中會調用 resolution.then ,然后同步到 promsie。但是這個整體的過程需要加入 microtask 隊列中等待運行,當這個任務運行時,如果 resolution 也是一個 Promise 的話,則 (value) => {ReslovePromise(promise, value) } 又會被作為一個 microtask 加入 microtask 隊列中等待運行。
你可以會疑惑為什么要這樣做?為什么不同步調用 resolution.then((value) => {ReslovePromise(promise, value) }),而是把他封裝為一個 microtask 呢?我一開始也感到疑惑,好在規范中給出了一個原因。

注意: 此作業使用提供的 thenable 及其 then 方法來解決給定的 Promise。 此過程必須作為作業進行,以確保在對任何周圍代碼的評估完成后對 then 方法進行評估。
引至 ECMAScript NewPromiseResolveThenableJobTask 規范(作者翻譯)

什么是 thenable:

Javascript 為了識別 Promise 產生的一個概念,簡單來說就是所有包含 then 方法的對象都是 thenable。

『以確保在對任何周圍代碼的評估完成后對 then 方法進行評估』指的是什么呢?我唯一能想到的就是下面這種情況。

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回調 會先進入 microtask 隊列,等待其執行時 調用 p1 的 resolve,但是參數是一個包含 then 方法的對象。這時 p1 不會立即改變為 fulfilled,而是創建一個 microtask 來執行這個then方法,然后將 p2的 onFulfilled 加入 microtask 隊列。這時 microtask 隊列中有兩個 microtask,一個是執行 resolve 返回值中的 then函數,另一個則是 p3的 onFulfilled 函數。
然后取出第一個 microtask 執行(取出后 microtask 隊列中只剩下 p3的 onFulfilled),執行后 p1 的狀態變為 fulfilled,然后 p1 的 onFulfilled 進入隊列。后面可想而知是相繼輸出 2和1(因為 p1 的 onFulfilled 函數在 p3 的 onFulfilled 函數之后進入 microtask 隊列)。
如果沒有將 NewPromiseResolveThenableJobTask 作為一個 microtask。也就變成了 p2.then 中的回調執行時同步觸發 resolve 參數中的 then 方法,fulfilled 的狀態會立即同步到 p1,這時 p1 的 onFulfilled 就會先進入 microtask,導致結果變為 12。這樣的執行結果可以會讓JavaScript開發者感到疑惑。
所以 ECMAScript 將其作為一個異步任務來執行。

似乎 返回 Promsie 對象會產生兩個 microtask 似乎會更讓人感到疑惑。

RejectPromiseReactionJob
PromiseReactionJob 中如果處理函數 handler 執行時拋出異常則會執行 RejectPromiseReactionJob,也就是下面這種情況

let p0 = Promise.resolve() let p1 = p0.then(() => {throw 'error'; // handler 執行時出錯 })

這是會調用 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,是一個 Promise 對象 // 執行 RejectPromise,調用 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 是類似的,就是調用 RejectPromise 來調用 Promsie 的 reject 方法,這個在上面 reject 的地方介紹過了。
PromiseReactionJob的handler == Undefined分支
PromiseReactionJob 中還有一個 handler == Undefined 的分支也很重要,當一個 task 中的 handler 為 undefined時會進入這個分支,為了方便閱讀,這里再貼一下代碼

transitioning macro PromiseReactionJob(context: Context, argument: JSAny, handler: Callable|Undefined,promiseOrCapability: JSPromise|PromiseCapability|Undefined,reactionType: constexpr PromiseReactionType): JSAny {if (handler == Undefined) {// 沒有處理函數的 case,透傳上一個 Promise 的 argumentif constexpr (reactionType == kPromiseReactionFulfill) {// 基本類同 JS 層的 resolvereturn FuflfillPromiseReactionJob(context, promiseOrCapability, argument, reactionType);} else {// 基本類同 JS 層的 rejectreturn RejectPromiseReactionJob(context, promiseOrCapability, argument, reactionType);}} else {// 刪除} }

進入分支后會直接獲取上一個 Promise 對象的 value 和 狀態 同步到當前 promise 來,我們來通過一段js了解他

let p0 = new Promise((resolve, reject) => {reject(123) }) // p0 的狀態為 rejectedlet p1 = p0.then(_ => {console.log('p0 onFulfilled')}) // p0 的 onRejected 作為 handler 進入 microtask 隊列 // 但是因為 then 沒有傳遞第二個參數 // 所以 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

同步代碼執行完畢后(執行過程大致如注釋),開啟取 microtask 執行,此時 microtask 隊列中只有一個 handler 為 undefined 的任務。進入 PromiseReactionJob 的 handler == Undefined 分支。
因為此時 p0 狀態為 rejected,所以執行 RejectPromiseReactionJob(context, promiseOrCapability, argument, reactionType),其中 promiseOrCapability 就是p1, argument 就是 p0 的值 123,reactionType 為 rejected。
執行后 p0 的狀態也變為 reactionType 也就是 rejected,p1 的值為 argument(相當于吧p0的狀態和值都轉移到了p1)。
然后執行 p1 的 reject 函數(FulfillPromise(p1, 123)),會吧 p1 綁定的 PromiseReaction 鏈表中的 onRejected(還是undefined) 當做 handler 進入microtask 隊列(因為 p1 的狀態是 rejected,所以是onRejected)
同樣還是取 microtask 任務執行,handler 還是 undefined,后面就和上面一樣,把狀態 rejected 和值 123 繼續同步給 p2,…
再次取 microtask 執行,因為 p2 綁定了 onRejected 函數,所以 handler 不是 undefined,則不走 handler == Undefined 分支,另一個分支的邏輯剛剛已經描述過了。大概就是執行 onRejected(123),然后將其結果設置到 p3 的value,p3 變為 fulfilled 狀態。

輸出 p2 onRejected

因為 onRejected(123) 的返回值是 undefined,所以 p3 變為 fulfilled 狀態,且值為 undefined
后面還是一樣的,但是 handler 就是 onFulfilled 了,因為 p3 的狀態是 fulfilled嘛,這里就相當于 onFulfilled(undefined)(因為 p3 的值的 undefined)。

輸出 p3 onFulfilled

而后 p4 的狀態也變成了 fulfilled,值也是 undefined,因為 p3 的 onFulfilled 返回值是 undefined
然后 p4 的 onFulfilled 變成 handler 隊列,因為 p4 沒有調用 then 綁定過 onFulfilled 處理函數。但是因為沒有調用 then 方法,所以也沒有產生新的 Promsie 對象,這次在執行 FuflfillPromiseReactionJob 方法的時候進入 promiseOrCapability 為 Undefined 分支就結束了
至此所有相關的任務全部執行完成
如果上面你看懂了,那么下面這段代碼我想你也應該能知道結果

Promise.resolve('123').then(() => {throw new Error('456')}).then(_ => {console.log('shouldnot be here')}).catch((e) => console.log(e)).then((data) => console.log(data));

catch(onRejected) 的本質是 then(undefined, onRejected)

這就是 Promise 的 rejected 傳遞機制,不斷向下傳遞直到遇見 onRejected 處理函數為止
Promise 的幾個高難度題目
題目1

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 6

主要考察 當 Promise 的值是 promsie 對象時會如何處理,在本文 的 then 的鏈式調用與 microtask 隊列> ResolvePromise 目錄末尾處開始介紹
關鍵字:thenable、NewPromiseResolveThenableJobTask

題解
為了方便描述,我們將上面的代碼轉化為下面這樣

let p1 = Promise.resolve() let p2 = p1.then(() => {console.log(0);let p3 = Promise.resolve(4)return p3; }) let p4 = p2.then((res) => {console.log(res) })let p5 = Promise.resolve() let p6 = p5.then(() => {console.log(1); }) let p7 = p6.then(() => {console.log(2); }) let p8 = p7.then(() => {console.log(3); }) let p9 = p8.then(() => {console.log(5); }) let p10 = p9.then(() => {console.log(6); })

先執行所有的同步代碼,執行過程如下面的注釋

let p1 = Promise.resolve() // 1. p1 的狀態為 fulfilledlet p2 = p1.then(() => {console.log(0);let p3 = Promise.resolve(4)return p3; }) // 2. 因為 p1 的狀態已經是 fulfilled,所以調用 then 后立即將 onFulfilled 放入 microtask 隊列 // 此時 microtask 只有p1的 onFulfilled: [p1.onFulfilled]let p4 = p2.then((res) => {console.log(res) }) // 3. p2的狀態還是 pending,所以調用 then 后是為 p2 收集依賴,此時 p2 的 reactions 如下 /*{onFulfilled: (res) => {console.log(res)},onRejected: undefined }*/let p5 = Promise.resolve() // 4. p5 的狀態為 fulfilledlet p6 = p5.then(() => {console.log(1); }) // 5. 同第2步,將 onFulfilled 加入 microtask 隊列 // 此時 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

當同步代碼執行完成后,microtask 隊列只有

[p1.onFulfilled, p5.onFulfilled]

然后取出 p1.onFulfilled 來執行,此時輸出 0,但是發現 p1.onFulfilled 返回值的 p3 是一個 Promise 對象。所以會執行 ResolvePromise 的 Enqueue 代碼塊,里面會調用 NewPromiseResolveThenableJobTask 產生一個微任務,這個微任務的要做的事情上面已經介紹過,大致就是下面這樣

let promiseResolveThenableJobTask = () => {p3.then((value) => { // p3的value是4ReslovePromise(p2, value) }) }

然后將其加入 microtask 隊列, 這時 microtask 隊列就變成了 :

[p5.onFulfilled, promiseResolveThenableJobTask]

繼續取出 p5.onFulfilled 執行,此時輸出 1,因為 p5.onFulfilled 返回值是 undefined,所以就將 undefined 作為 p6 的值,然后將 p6 的狀態變為 fulfilled。

因為 p6 的狀態被改變,所以它的 reactions 也會加入 microtask 隊列,這時 microtask 隊列就變成這樣:

[promiseResolveThenableJobTask,p6.onFulfilled]

復制代碼

同樣是取 promiseResolveThenableJobTask 執行,因為 promiseResolveThenableJobTask 的內容是下面這樣

let promiseResolveThenableJobTask = () => {p3.then((value) => { ReslovePromise(p2, value) // ReslovePromise 的作用上面有介紹}) }

所以執行 promiseResolveThenableJobTask 時就相當于執行了 p3.then((value) => {ReslovePromise(p2, value)})
因為 p3 的狀態是 fulfilled ,所以會將其 onFulfilled 加入 microtask 隊列(value參數就是 p3 的值 4,后序他將傳遞給p2),這時 microtask 隊列就變成這樣:

[p6.onFulfilled,p3.onFulfilled]

同樣是取 p6.onFulfilled 執行,然后輸出 2 并將其返回值 undefined 設置為 p7 的值,并將 p7 變為 fulfilled 狀態,所以 p7 的 reactions 也會加入 microtask 隊列,這時 microtask 隊列就變成這樣:

[p3.onFulfilled,p7.onFulfilled]

p3.onFulfilled 出隊執行,p3.onFulfilled 是 (value) => {ReslovePromise(p2, value)}, 參數 value 是 4,所以此時就執行 ReslovePromise(p2, 4),這就相當于調用了 p2 的 resolve。

所以此時 p2 的 值變為 4, 狀態為變 fulfilled,然后將其 reactions 挨個加入 microtask 隊列,這時 microtask 隊列就變成這樣:

[p7.onFulfilled,p2.onFulfilled]

p7.onFulfilled 出隊列執行,輸出 3,p8 狀態變為 fulfille,值變為 undefined,然后 p8.onFulfilled 加入隊列

[p2.onFulfilled,p8.onFulfilled]

p2.onFulfilled 出隊列執行,輸出 4,因為 p2 沒有被在此調用 then 方法,所以就沒有產生下一個 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

與上題知識點一致,考察的是 Promise 的值是一個包含 then 方法的對象時發生的邏輯
關鍵字: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 // 輸出報錯 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

為什么第一種方式會報錯?
考察的是規范中的 HostPromiseRejectionTracker,當一個沒有綁定處理函數的 Promsie 被調用 reject 則會創建一個 微任務來再次檢測這個 Promise 是否存在處理函數,如果此時還不存在則輸出報錯,setTimeout回調執行在微任務之后。
本文 reject > HostPromiseRejectionTracker 目錄處有詳細介紹。

注意: 瀏覽器控制臺有一個非常奇怪的特性,如果在這個錯誤輸出后在為其綁定 onrejected 處理函數,瀏覽器會將控制臺的錯誤覆蓋掉。所以如果你在瀏覽器執行這段代碼,請將setTimeout的時間設置長一點,這樣效果更加容易肉眼可見。

題目四
為什么 async1 end 輸出在 promise3 之后?

async function async1() { console.log("async1 start"); await async2(); console.log("async1 end"); } async function async2() { console.log("async2"); return Promise.resolve().then(() => { console.log("async2-inner"); }); } console.log("script start"); setTimeout(function () { console.log("settimeout"); }); async1(); new Promise(function (resolve) { console.log("promise1"); resolve(); }) .then(function () { console.log("promise2"); }) .then(function () { console.log("promise3"); }) .then(function () { console.log("promise4"); }); console.log("script end");

題解
這個問題,這里面涉及 async 中使用 await 會產生多個Promise鏈路的問題以及 resolve 值為 Promise 對象的問題,重點看 async2,我把他轉化為這樣(setTimeout對于本題懸念不大,就刪除了)。

async function async1() {console.log("async1 start");let a2 = async2();await a2console.log("async1 end"); } async function async2() {console.log("async2");let p1 = Promise.resolve()let p2 = p1.then(() => {console.log("async2-inner");})return p2; }let a1 = async1();let p3 = new Promise(function (resolve) {console.log("promise1");resolve(); })let p4 = p3.then(function () {console.log("promise2"); }) // 為 p3 添加 reactions let p5 = p4.then(function () {console.log("promise3"); }) // 為 p4 添加 reactions let p6 = p5.then(function () {console.log("promise4"); }); // 為 p5 添加 reactions console.log("script end");

想必你也發現了 async 函數中 await 語句之前的代碼都是同步執行的(相當于Promsie的executor)

先調用 async1,輸出 async1 start, 同步執行到 async2() 的位置,然后去同步執行 async2。

然后輸出 async2,里面創建一個 fulfilled 狀態的 p1,然后為 p1 綁定 then,因為 p1 是 fulfilled 狀態所以 p1.onFulfilled 會立即進入 microtask 隊列。這時 microtask 隊列就變成這樣:

[p1.onFulfilled]

然后返回 p2(resolve(p2)),又因為 p2 是一個 Promise 對象,所以創建一個如下的 promiseResolveThenableJobTask

let promiseResolveThenableJobTask = () => {p2.then((value) => { ReslovePromise(a2, value) // ReslovePromise 的作用上面有介紹}) } [p1.onFulfilled,promiseResolveThenableJobTask]

然后執行 await a2,這里很關鍵,await 會等待一個 Promsie 進入 fulfilled 狀態后才執行后面的代碼,其實就相當于下面這樣

a2.then(_ => {// 這里是 async1 中 await 后的代碼 }) // 為 a2 綁定了 reactions,這里的 onFulfill 暫時就叫 『async1后半段』吧 // 其實這里面做的事情很多,我后面可能會單獨講解 async/await 的原理

注意此時 a2 還不是 fulfilled 狀態,因為他需要等待 promiseResolveThenableJobTask 執行時來調用他的 resolve 才會變成 fulfilled。

這時 async1() 觸發的同步代碼才執行完畢,繼續執行后面的 new Promise

同步執行這段代碼

function (resolve) {console.log("promise1");resolve(); }

輸出 promise1, 執行 resolve, 然后 p3 狀態變為 fulfilled,p3.onFulfilled 進入隊列,后面的 then 都是給對應的 promsie 綁定 reactions 這個就不說了,最后輸出 script end
到此時所有同步代碼執行完成, microtask 隊列是這樣的:

[p1.onFulfilled,promiseResolveThenableJobTask,p3.onFulfilled]

至此所有同步代碼執行完成,開始取 microtask 執行,首先是 p1.onFulfilleed ,執行輸出 async2-inner, 然后 將其返回值 undefined 作為 p2 的值,并將 p2 變成 fulfilled 狀態。因為 p2 此時沒有 reactions (也就是沒有被調用過then方法),所以不會發生什么事情

[promiseResolveThenableJobTask,p3.onFulfilled]

promiseResolveThenableJobTask 出隊列執行, 其內容如下,上面已經說過了

let promiseResolveThenableJobTask = () => {p2.then((value) => { ReslovePromise(a2, value) // ReslovePromise 的作用上面有介紹}) }

執行是執行 p2 的 then 方法為其綁定 onFulfilled 處理函數,但是 p2 已經是 fulfilled 狀態,所以會直接將 p2.onFulfilled 加入 microtask 隊列。

[p3.onFulfilled, p2.onFulfilled]

p3.onFulfilled 出隊執行,輸出 promise2 ,將 p4 的狀態變為 fulfilled,p4的值為其1返回值也就是undefined,然后 p4 的 onFulfilled 也會加入 microtask 隊列

[p2.onFulfilled, p4.onFulfilled]

p2.onFulfilled 出隊列執行, p2.onFulfilled 的內容如下,這個上面說過

(value) => { ReslovePromise(a2, value) // 也就是 a2 的 resolve(value) }

所以執行 ReslovePromise 后,a2 會變成 fulfilled 狀態,a2.onFulfilled 也就是 『async1后半段』 也理所當然的進入 microtask 隊列

[p4.onFulfilled, async1后半段]

后面的結果就沒有什么難點了,p4.onFulfilled 出隊執行,輸出 promise3,然后 p5.onFulfilled 入隊

[async1后半段,p5.onFulfilled]

async1后半段 出隊執行,輸出 async1 end, 然后 a1 狀態變為 fulfilled,但是沒有綁定任何處理函數,所以 a1 就沒有后續了

[p5.onFulfilled]

p5.onFulfilled 出隊執行輸出 promise4,同理 p6 沒有綁定任何處理函數,至此所有代碼執行完成

總結

以上是生活随笔為你收集整理的V8 Promise源码全面解读的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。