V8 引擎 Promise 源碼全面解讀(深度好文)

作者:月夕

原文:https://juejin.cn/post/7055202073511460895

寫在前面的話

閱讀本文你將收穫什麼?

你知道 瀏覽器 & 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 6


new 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 中的一部分, 而 JavaScriptPromise 的實現規範並非來源於 Promise/A+,而是來自 ECMAScript 規範。

所以要知道這個問題的答案,我們不能僅僅看 Promise/A+ ,對於代碼的執行過程和順序我們的關注點應該是在 ECMAScript 或者 V8 上。

接下來我就結合 ECMAScript 規範來對 V8Promise 源碼進行全面的解讀。

還有三件事需要提前說一下:

  1. 本文更加適合有一定 Promise 基礎的同學閱讀,如果對 Promise 不瞭解的同學可以先看看這些文章
  1. 對於後續貼出的 c++ 代碼大家只需要着重看帶有中文註釋的地方即可

  2. 因爲代碼塊不會自動換行,所以建議 PC 端閱讀可以有更好的閱讀體驗

  3. 文章很長,可以收藏,有時間靜下心來慢慢看也可以。

  4. 點贊👍🏻、點贊👍🏻、點贊👍🏻

進入正題

PromiseState

Promise 的 3 種狀態,pendingfulfilledrejected ,源碼如下 [3]:

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

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

JSPromise

JSPromise 描述 Promise 的基本信息,源碼如下 [4]:

bitfield struct JSPromiseFlags extends uint31 {
  // Promise 的狀態,kPending/kFulfilled/kRejected
  status: 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 狀態;

其它

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

構造函數

構造函數源碼如下 [5]:

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 規範 [6]

PromisePrototypeThen

Promisethen 方法傳入兩個回調函數 onFulfilledonRejected,分別用於處理 fulfilledrejected 狀態,並返回一個新的 Promise

JavaScript 層的 then 函數實際上是 V8 中的 PromisePrototypeThen 函數,源碼如下 [7]:

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 接收的兩個參數
  // 如果不傳則默認值爲 Undefined
  const onFulfilled = CastOrDefault<Callable>(onFulfilled, Undefined);
  const onRejected = CastOrDefault<Callable>(onRejected, Undefined);

  // 調用 PerformPromiseThenImpl 函數
  PerformPromiseThenImpl(
      promise, onFulfilled, onRejected, resultPromiseOrCapability);
  // 返回一個新的 Promise
  return 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 規範 [8]

PerformPromiseThenImpl 有 4 個參數,因爲 PerformPromiseThenImpl 是在調用 then 調用,所以它的前三個參數分別是被調用 then 方法的 Promise 對象,以及這個 Promise 對象即將被綁定的兩個處理函數 onFulfilledonRejected(值就是調用 then(onFulfilled, onRejected) 時傳遞的兩個參數),最後一個參數爲調用這個 then 返回的新 Promise 對象 resultPromiseOrCapability

PerformPromiseThenImpl 源碼如下 [9]:

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 的處理函數,並賦值給 JSPromisereactions_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 函數源碼如下 [10]:

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 隊列。

reactions

圖中使用 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 規範 [11] 中的 HostPromiseRejectionTracker(promise, "handle")HostPromiseRejectionTracker 是一個抽象方法,這表示沒有規定它的具體的邏輯。大致的作用是標記一下 promise 已經綁定了 rejected 狀態的處理函數。不用疑惑爲什麼要這麼做,後面會單獨重點說。

注意 1 HostPromiseRejectionTracker 在兩種情況下被調用:

當一個 promise 在沒有任何處理程序的情況下被拒絕時,它的操作參數設置爲 “reject”。

當第一次將處理程序添加到被拒絕的 Promise 中時,將調用它並將其操作參數設置爲 “handle”。

引至 ——ECMAScript 規範 12

小結

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

  2. 然後根據當前 promise 的不同狀態執行不同的邏輯

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

  2. 最後返回新的 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 規範 [13]

reslove(value) 就是規範中的 FulfillPromise(promise, value) 函數,他的作用是將一個 Promise 的狀態由 pending 改變爲 fulfilled,並且將這個 Promise 的所有處理函數都變成 microtask 加入到 microtask 隊列中等待執行。

resolve 函數歸根到底調用了 V8 的 FulfillPromise 函數,源碼如下 [14]:

// 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 的狀態爲 fulfilled
  promise.SetStatus(PromiseState::kFulfilled);

  // Promise 的處理函數,Promise 的結果都拿到了,開始正式處理
  TriggerPromiseReactions(reactions, value, kPromiseReactionFulfill);
  return Undefined;
}

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

TriggerPromiseReactions

源碼如下 [15]:

// 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 做了兩件事:

MorphAndEnqueuePromiseReaction

MorphAndEnqueuePromiseReaction[16] 將 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。

在調用 resolve 之前 reactions_or_result 其實是一個鏈表,存儲的是當前 Promise 的所有處理函數,因爲 promise 在使用 then 收集依賴時是將最新的依賴存放到鏈表頭部,所以還需要先對鏈表進行反轉,然後將其挨個放入 microtask 隊列中等待執行

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

reject

reject 與 reslove 沒什麼太大差別

ECMAScript 規範 [17]

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

上述代碼 5s 後執行 reject 函數,控制檯打印 rejected。

RejectPromise

ECMAScript 規範 [18]

reject(season) 函數調用了 V8 的 RejectPromise(promise, season) 函數,源碼如下 [19] :

// https://tc39.es/ecma262/#sec-rejectpromise
transitioning builtin
RejectPromise(implicit context: Context)(
    promise: JSPromise, reason: JSAny, debugEvent: Boolean): JSAny {
 
  // 如果當前 Promise 沒有綁定處理函數,
  // 則會調用 runtime::RejectPromise
  if (IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate() ||
      !promise.HasHandler()) {
    return runtime::RejectPromise(promise, reason, debugEvent);
  }
 
  // 取出 Promise 的處理對象 PromiseReaction
  const reactions =
      UnsafeCast<(Zero | PromiseReaction)>(promise.reactions_or_result);
  // 這裏的 reason 就是 reject 函數的參數
  promise.reactions_or_result = reason;
  // 設置 Promise 的狀態爲 rejected
  promise.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') ),如果沒有則拋出錯誤,如果有則什麼也不發生。

// 不可運行的js僞代碼
function HostPromiseRejectionTracker(promise, status) {
  if (status === 'handle') {
    promise.HasHandler = true
  } else if (status === 'reject'){
    promise.catch(() ={
      if (!promise.HasHandler) {
        throw new Error('Uncaught (in promise) ' + promise.value)
      }
    })
  }
}

RejectPromise(){
  //...
  if (!promise.HasHandler) {
    HostPromiseRejectionTracker(promise, 'reject')
  }
  //...
}

FulfillPromise(){
  //...
  if (!promise.HasHandler) {
    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 步:

catch

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

PromisePrototypeCatch

以上面代碼爲例,當 catch 方法執行時,調用了 V8 的 PromisePrototypeCatch[20] 方法,源碼如下:

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[21] 源碼如下:

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);
  } else
    deferred {
      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[22] ,由於 microtask 的類型由很多種,所以 RunSingleMicrotask 的分支有許多。這裏就不列出代碼了。

PromiseReactionJob

在執行 microtask 的過程中,MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask 會調用 PromiseReactionJob[23],源碼如下:

transitioning
macro PromiseReactionJob(
    context: Context, argument: JSAny, handler: Callable|Undefined,
    promiseOrCapability: JSPromise|PromiseCapability|Undefined,
    reactionType: constexpr PromiseReactionType): JSAny {
  if (handler == Undefined) {
    // 沒有處理函數的 case,透傳上一個 Promise 的 argument
    if constexpr (reactionType == kPromiseReactionFulfill) {
      // 基本類同 JS 層的 resolve
      return FuflfillPromiseReactionJob(
          context, promiseOrCapability, argument, reactionType);
    } else {
      // 基本類同 JS 層的 reject
      return RejectPromiseReactionJob(
          context, promiseOrCapability, argument, reactionType);
    }
  } else {
    try {
      // 試圖調用 Promise 處理函數,相當於 handler(argument)
      const result =
          Call(context, UnsafeCast<Callable>(handler), Undefined, argument);
        // 基本類同 JS 層的 resolve
        return 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 的狀態爲 reject

let 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[24],這個方法很重要,他是規範中的 Promise Resolve Functions[25],他的作用是同步當前處理函數的結果(值和狀態)給其產生的 promsie。promiseOrCapability。

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

ResolvePromise(重要)

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

// https://tc39.es/ecma262/#sec-promise-resolve-functions
transitioning builtin
ResolvePromise(implicit context: Context)(
    promise: JSPromise, resolution: JSAny): JSAny {
 // 刪
  let thenObject = Undefined;
  try {
    // 調用 FulfillPromise
    const 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)) {
      // 將執行結果同步到 promise
      return 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 規範 26

什麼是 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 的 argument
    if constexpr (reactionType == kPromiseReactionFulfill) {
      // 基本類同 JS 層的 resolve
      return FuflfillPromiseReactionJob(
          context, promiseOrCapability, argument, reactionType);
    } else {
      // 基本類同 JS 層的 reject
      return RejectPromiseReactionJob(
          context, promiseOrCapability, argument, reactionType);
    }
  } else {
    // 刪除
  }
}

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

let p0 = new Promise((resolve, reject) ={
    reject(123)
})
// p0 的狀態爲 rejected

let p1 = p0.then(_ ={console.log('p0 onFulfilled')})
// p0 的 onRejected 作爲 handler 進入 microtask 隊列
// 但是因爲 then 沒有傳遞第二個參數
// 所以 onRejected 是 undefined,那麼 handler 也是 undefined

let 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 目錄末尾處開始介紹

關鍵字:thenableNewPromiseResolveThenableJobTask

題解

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

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 的狀態爲 fulfilled

let 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 的狀態爲 fulfilled

let 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 添加 reactions

let p8 = p7.then(() ={
    console.log(3);
})
// 7. 同上,是給 p7 添加 reactions

let p9 = p8.then(() ={
    console.log(5);
})
// 8. 同上,是給 p8 添加 reactions

let p10 = p9.then(() ={
    console.log(6);
})
// 9. 同上,是給 p9 添加 reactions
  1. 當同步代碼執行完成後,microtask 隊列只有
[p1.onFulfilled, p5.onFulfilled]
  1. 然後取出 p1.onFulfilled 來執行,此時輸出 0,但是發現 p1.onFulfilled 返回值的 p3 是一個 Promise 對象。所以會執行 ResolvePromise 的 Enqueue 代碼塊,裏面會調用 NewPromiseResolveThenableJobTask 產生一個微任務,這個微任務的要做的事情上面已經介紹過,大致就是下面這樣
let promiseResolveThenableJobTask = () ={
    p3.then((value) ={ // p3的value是4
        ReslovePromise(p2, value) 
    })
}

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

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

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

[promiseResolveThenableJobTask,p6.onFulfilled]
  1. 同樣是取 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]
  1. 同樣是取 p6.onFulfilled 執行,然後輸出 2 並將其返回值 undefined 設置爲 p7 的值,並將 p7 變爲 fulfilled 狀態,所以 p7 的 reactions 也會加入 microtask 隊列,這時 microtask 隊列就變成這樣:
[p3.onFulfilled,p7.onFulfilled]
  1. 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]
  1. p7.onFulfilled 出隊列執行,輸出 3,p8 狀態變爲 fulfille,值變爲 undefined,然後 p8.onFulfilled 加入隊列
[p2.onFulfilled,p8.onFulfilled]
  1. p2.onFulfilled 出隊列執行,輸出 4,因爲 p2 沒有被在此調用 then 方法,所以就沒有產生下一個 Promise 對象,所以也就沒有後序了。
[p8.onFulfilled]
  1. 後面就不用說了

題目 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 方法的對象時發生的邏輯

關鍵字:thenableNewPromiseResolveThenableJobTask

題目 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 either

const 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 a2
    console.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)

  1. 先調用 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]
  1. 然後執行 await a2,這裏很關鍵,await 會等待一個 Promsie 進入 fulfilled 狀態後才執行後面的代碼,其實就相當於下面這樣
a2.then(_ ={
 // 這裏是 async1 中 await 後的代碼
})
// 爲 a2 綁定了 reactions,這裏的 onFulfill 暫時就叫 『async1後半段』吧
// 其實這裏面做的事情很多,我後面可能會單獨講解 async/await 的原理

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

  1. 這時 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]
  1. 至此所有同步代碼執行完成,開始取 microtask 執行,首先是 p1.onFulfilleed ,執行輸出 async2-inner, 然後 將其返回值 undefined 作爲 p2 的值,並將 p2 變成 fulfilled 狀態。因爲 p2 此時沒有 reactions (也就是沒有被調用過 then 方法),所以不會發生什麼事情
[promiseResolveThenableJobTask,p3.onFulfilled]
  1. promiseResolveThenableJobTask 出隊列執行, 其內容如下,上面已經說過了
let promiseResolveThenableJobTask = () ={
    p2.then((value) ={ 
        ReslovePromise(a2, value) // ReslovePromise 的作用上面有介紹
    })
}

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

[p3.onFulfilled, p2.onFulfilled]
  1. p3.onFulfilled 出隊執行,輸出 promise2 ,將 p4 的狀態變爲 fulfilled,p4 的值爲其 1 返回值也就是 undefined,然後 p4 的 onFulfilled 也會加入 microtask 隊列
[p2.onFulfilled, p4.onFulfilled]
  1. p2.onFulfilled 出隊列執行, p2.onFulfilled 的內容如下,這個上面說過
(value) ={ 
    ReslovePromise(a2, value) // 也就是 a2 的 resolve(value)
}

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

[p4.onFulfilled, async1後半段]
  1. 後面的結果就沒有什麼難點了,p4.onFulfilled 出隊執行,輸出 promise3,然後 p5.onFulfilled 入隊
[async1後半段,p5.onFulfilled]
  1. async1 後半段 出隊執行,輸出 async1 end, 然後 a1 狀態變爲 fulfilled,但是沒有綁定任何處理函數,所以 a1 就沒有後續了
[p5.onFulfilled]
  1. p5.onFulfilled 出隊執行輸出 promise4,同理 p6 沒有綁定任何處理函數,至此所有代碼執行完成

相關鏈接

Promise V8 源碼分析 (一)——徐鵬躍 [28]

promise.then 中 return Promise.resolve 後,發生了什麼?[29]

Chromium Code Search[30]

ECMA-262, 11th edition, June 2020[31]

歡迎關注「React」

參考資料

[1]

https://juejin.cn/post/7054931900074295310#heading-7: https://juejin.cn/post/7054931900074295310#heading-7

[2]

https://juejin.cn/post/6844903607968481287: https://juejin.cn/post/6844903607968481287

[3]

https://source.chromium.org/chromium/chromium/src/+/main:v8/src/builtins/base.tq#190: https://link.juejin.cn?target=https%3A%2F%2Fsource.chromium.org%2Fchromium%2Fchromium%2Fsrc%2F%2B%2Fmain%3Av8%2Fsrc%2Fbuiltins%2Fbase.tq%23190

[4]

https://source.chromium.org/chromium/chromium/src/+/main:v8/src/objects/js-promise.tq#13: https://link.juejin.cn?target=https%3A%2F%2Fsource.chromium.org%2Fchromium%2Fchromium%2Fsrc%2F%2B%2Fmain%3Av8%2Fsrc%2Fobjects%2Fjs-promise.tq%2313

[5]

https://source.chromium.org/chromium/chromium/src/+/main:v8/src/builtins/promise-constructor.tq#47: https://link.juejin.cn?target=https%3A%2F%2Fsource.chromium.org%2Fchromium%2Fchromium%2Fsrc%2F%2B%2Fmain%3Av8%2Fsrc%2Fbuiltins%2Fpromise-constructor.tq%2347

[6]

https://262.ecma-international.org/11.0/#sec-promise.prototype.then: https://link.juejin.cn?target=https%3A%2F%2F262.ecma-international.org%2F11.0%2F%23sec-promise.prototype.then

[7]

https://source.chromium.org/chromium/chromium/src/+/main:v8/src/builtins/promise-then.tq#21: https://link.juejin.cn?target=https%3A%2F%2Fsource.chromium.org%2Fchromium%2Fchromium%2Fsrc%2F%2B%2Fmain%3Av8%2Fsrc%2Fbuiltins%2Fpromise-then.tq%2321

[8]

https://262.ecma-international.org/11.0/#sec-performpromisethen: https://link.juejin.cn?target=https%3A%2F%2F262.ecma-international.org%2F11.0%2F%23sec-performpromisethen

[9]

https://source.chromium.org/chromium/chromium/src/+/main:v8/src/builtins/promise-abstract-operations.tq#409: https://link.juejin.cn?target=https%3A%2F%2Fsource.chromium.org%2Fchromium%2Fchromium%2Fsrc%2F%2B%2Fmain%3Av8%2Fsrc%2Fbuiltins%2Fpromise-abstract-operations.tq%23409

[10]

https://source.chromium.org/chromium/chromium/src/+/main:v8/src/builtins/promise-misc.tq#134: https://link.juejin.cn?target=https%3A%2F%2Fsource.chromium.org%2Fchromium%2Fchromium%2Fsrc%2F%2B%2Fmain%3Av8%2Fsrc%2Fbuiltins%2Fpromise-misc.tq%23134

[11]

https://262.ecma-international.org/11.0/#sec-performpromisethen: https://link.juejin.cn?target=https%3A%2F%2F262.ecma-international.org%2F11.0%2F%23sec-performpromisethen

[12]

https://262.ecma-international.org/11.0/#sec-host-promise-rejection-tracker: https://link.juejin.cn?target=https%3A%2F%2F262.ecma-international.org%2F11.0%2F%23sec-host-promise-rejection-tracker

[13]

https://262.ecma-international.org/11.0/#sec-fulfillpromise: https://link.juejin.cn?target=https%3A%2F%2F262.ecma-international.org%2F11.0%2F%23sec-fulfillpromise

[14]

https://source.chromium.org/chromium/chromium/src/+/main:v8/src/builtins/promise-abstract-operations.tq#182: https://link.juejin.cn?target=https%3A%2F%2Fsource.chromium.org%2Fchromium%2Fchromium%2Fsrc%2F%2B%2Fmain%3Av8%2Fsrc%2Fbuiltins%2Fpromise-abstract-operations.tq%23182

[15]

https://source.chromium.org/chromium/chromium/src/+/main:v8/src/builtins/promise-abstract-operations.tq#140: https://link.juejin.cn?target=https%3A%2F%2Fsource.chromium.org%2Fchromium%2Fchromium%2Fsrc%2F%2B%2Fmain%3Av8%2Fsrc%2Fbuiltins%2Fpromise-abstract-operations.tq%23140

[16]

https://source.chromium.org/chromium/chromium/src/+/main:v8/src/builtins/promise-abstract-operations.tq#84: https://link.juejin.cn?target=https%3A%2F%2Fsource.chromium.org%2Fchromium%2Fchromium%2Fsrc%2F%2B%2Fmain%3Av8%2Fsrc%2Fbuiltins%2Fpromise-abstract-operations.tq%2384

[17]

https://262.ecma-international.org/11.0/#sec-rejectpromise: https://link.juejin.cn?target=https%3A%2F%2F262.ecma-international.org%2F11.0%2F%23sec-rejectpromise

[18]

https://262.ecma-international.org/11.0/#sec-rejectpromise: https://link.juejin.cn?target=https%3A%2F%2F262.ecma-international.org%2F11.0%2F%23sec-rejectpromise

[19]

https://source.chromium.org/chromium/chromium/src/+/main:v8/src/builtins/promise-abstract-operations.tq#210: https://link.juejin.cn?target=https%3A%2F%2Fsource.chromium.org%2Fchromium%2Fchromium%2Fsrc%2F%2B%2Fmain%3Av8%2Fsrc%2Fbuiltins%2Fpromise-abstract-operations.tq%23210

[20]

https://source.chromium.org/chromium/chromium/src/+/main:v8/src/builtins/promise-constructor.tq#100: https://link.juejin.cn?target=https%3A%2F%2Fsource.chromium.org%2Fchromium%2Fchromium%2Fsrc%2F%2B%2Fmain%3Av8%2Fsrc%2Fbuiltins%2Fpromise-constructor.tq%23100

[21]

https://source.chromium.org/chromium/chromium/src/+/main:v8/src/builtins/promise-misc.tq#199: https://link.juejin.cn?target=https%3A%2F%2Fsource.chromium.org%2Fchromium%2Fchromium%2Fsrc%2F%2B%2Fmain%3Av8%2Fsrc%2Fbuiltins%2Fpromise-misc.tq%23199

[22]

https://source.chromium.org/chromium/chromium/src/+/main:v8/src/builtins/builtins-microtask-queue-gen.cc#114: https://link.juejin.cn?target=https%3A%2F%2Fsource.chromium.org%2Fchromium%2Fchromium%2Fsrc%2F%2B%2Fmain%3Av8%2Fsrc%2Fbuiltins%2Fbuiltins-microtask-queue-gen.cc%23114

[23]

https://source.chromium.org/chromium/chromium/src/+/main:v8/src/builtins/promise-reaction-job.tq#43: https://link.juejin.cn?target=https%3A%2F%2Fsource.chromium.org%2Fchromium%2Fchromium%2Fsrc%2F%2B%2Fmain%3Av8%2Fsrc%2Fbuiltins%2Fpromise-reaction-job.tq%2343

[24]

https://chromium.googlesource.com/v8/v8.git/+/refs/heads/9.0-lkgr/src/builtins/promise-resolve.tq#88: https://link.juejin.cn?target=https%3A%2F%2Fchromium.googlesource.com%2Fv8%2Fv8.git%2F%2B%2Frefs%2Fheads%2F9.0-lkgr%2Fsrc%2Fbuiltins%2Fpromise-resolve.tq%2388

[25]

https://262.ecma-international.org/11.0/#sec-promise-resolve-functions: https://link.juejin.cn?target=https%3A%2F%2F262.ecma-international.org%2F11.0%2F%23sec-promise-resolve-functions

[26]

https://262.ecma-international.org/11.0/#sec-newpromisereactionjob: https://link.juejin.cn?target=https%3A%2F%2F262.ecma-international.org%2F11.0%2F%23sec-newpromisereactionjob

[27]

https://source.chromium.org/chromium/chromium/src/+/main:v8/src/builtins/: https://link.juejin.cn?target=https%3A%2F%2Fsource.chromium.org%2Fchromium%2Fchromium%2Fsrc%2F%2B%2Fmain%3Av8%2Fsrc%2Fbuiltins%2F

[28]

https://zhuanlan.zhihu.com/p/264944183: https://link.juejin.cn?target=https%3A%2F%2Fzhuanlan.zhihu.com%2Fp%2F264944183

[29]

https://www.zhihu.com/question/453677175/answer/1841325386: https://link.juejin.cn?target=https%3A%2F%2Fwww.zhihu.com%2Fquestion%2F453677175%2Fanswer%2F1841325386

[30]

https://source.chromium.org/chromium/chromium/src: https://link.juejin.cn?target=https%3A%2F%2Fsource.chromium.org%2Fchromium%2Fchromium%2Fsrc

[31]

https://262.ecma-international.org/11.0/: https://link.juejin.cn?target=https%3A%2F%2F262.ecma-international.org%2F11.0%2F

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/4ypJn3Ab8_6TIFtofgdBkQ