Promise/A+ 規範 - 中文版本

顾平安發表於2024-12-24

Promises/A+

這是一個開放標準,旨在讓不同開發者實現的 JavaScript Promise 能夠無縫銜接並應用——由前輩們制定,為其他後來者提供參考

一個 promise 所表示的是非同步操作的結果。與 promise 互動的主要方式是透過它的 then 方法,該方法會註冊所傳入的回撥函式,回撥函式將接收 promise 的最終值或者 promise 無法被滿足時的原因。

本規範詳細說明了 then 方法的行為,為所有符合 Promises/A+ 標準的 promise 實現提供可靠的基礎。因此本規範的更新或修訂應被視為非常穩定。雖然 Promises/A+ 組織可能會偶爾對本規範進行向後相容的小幅度修訂,以解決新發現的邊緣情況,但我們依然會在經過認真仔細的考慮、討論和測試後,才會去整合重大或者不向後相容的變更。

在過去,Promises/A+ 不僅明確早期 Promise/A 提案中的涉及的行為條款,而且還擴充套件了一些涵蓋實際行為的內容,並且省略了那些不清不楚或存在問題的部分。

最後要說的是,Promises/A+ 規範的核心並不涉及如何建立、滿足或拒絕 promise,而是選擇專注於提供一個強大的 then 方法以確保不同實現之間的相容性。大概未來的相關規範才可能會涉及如何建立、滿足和拒絕 promise 這些主題吧。

1 專業術語

  1. thenable: 一個具有 then 方法的物件或函式。
  2. promise:一個具有 then 方法的物件或函式,then 方法的行為符合本規範。
  3. value:是一個合法的 JS 值,含 undefinednullthenablepromise 等。
  4. exception:透過 throw 語句丟擲的值。
  5. reason:通常用於表示 promise 被拒絕/無法實現的原因,也是個值。

2 詳細規範

2.1 Promise 狀態

一個 promise 必須處於以下三種狀態之一:

待定中(pending)已實現(fulfilled)已拒絕(rejected)

關於這些狀態的描述如下:

  1. 待定中 —— pending:
    1. 可以轉變為其它兩種狀態,也就是已實現(fulfilled)或已拒絕(rejected)
  2. 已實現 —— fulfilled:
    1. 禁止從該狀態轉變為其它狀態,狀態不可改變
    2. 必須有一個不可改變的值(value —— 完成後的成果!!)
  3. 已拒絕 —— rejected:
    1. 禁止從該狀態轉變為其它狀態,狀態不可改變
    2. 必須有一個不可改變的原因(reason —— 為什麼拒絕!?)

注意:不可改變並不意味著深層次的不可變(比如屬性描述符中 writeable: false),你可以透過對當前狀態進行全等判斷(===),從而更改相應的值/原因和狀態後,那麼不可改變自然而然的就會成立了。

2.2 then 方法

一個 promise 必須提供一個 then 方法,它可以訪問當前或最終的值或者原因。

promisethen 方法接受兩個引數:

promise.then(onFulfilled, onRejected)
  1. onFulfilledonRejected 均是可選引數:

    1. 如果 onFulfilled 不是一個函式,則必須被忽略。
    2. 如果 onRejected 不是一個函式,則必須被忽略。

    被忽略是指我們不對它做任何的處理,不會丟擲任何的錯誤

  2. 如果 onFulfilled 是一個函式:

    1. 必須在 promise 被實現後呼叫,且以 promise 的值(value)作為第一個引數。
    2. promise 被實現之前不可呼叫。
    3. 不允許被多次呼叫。
  3. 如果 onRejected 是一個函式:

    1. 必須在 promise 被拒絕後呼叫,且以 promise 的原因(reason)作為第一個引數。
    2. promise 被拒絕之前不可呼叫。
    3. 不允許被多次呼叫。
  4. onFulfilledonRejected 必須在執行上下文棧僅包含平臺程式碼時才被呼叫。[3.1]

  5. onFulfilledonRejected 必須作為函式呼叫(沒有 this 值)。[3.2]

  6. then 可以在同一個 promise 上被多次呼叫。

    1. promise 被實現時,所有相應的 onFulfilled 回撥必須按它們呼叫 then 的順序執行。
    2. promise 被拒絕時,所有相應的 onRejected 回撥必須按它們呼叫 then 的順序執行。
  7. then 必須返回一個 promise [3.3]。

    promise2 = promise1.then(onFulfilled, onRejected);
    
    1. 如果 onFulfilledonRejected 返回一個值 x,則執行 Promise 解決過程 [[Resolve]](promise2, x)[2.3]。
    2. 如果 onFulfilledonRejected 丟擲異常 e,則 promise2 必須以 e 作為原因被拒絕。
    3. 如果 promise1 被實現且 onFulfilled 不是一個函式,那麼 promise2 必須以與 promise1 相同的值(value)被實現。
    4. 如果 promise1 被拒絕且 onRejected 不是一個函式,那麼 promise2 必須以與 promise1 相同的原因(reason)被拒絕。

2.3 Promise 解決過程

Promise 解決過程 是一種抽象操作,它接受一個 promise 和一個值作為輸入,表示為 [[Resolve]](promise, x)。如果 x 是一個 thenable,則會嘗試使 promise 採用 x 的狀態,前提是 x 至少在某種程度上表現得像一個 promise。否則,它將用值 x 來實現 promise

thenable 的這種處理方式使得不同的 promise 實現能夠有效相容,只要它們提供符合 Promises/A+ 標準的 then 方法。但是也允許 Promises/A+ 實現能夠“接納”那些具有合理 then 方法的非標準實現。

要實現 [[Resolve]](promise, x),請按照以下步驟操作:

  1. 如果 promisex 指向同一個物件,則以 TypeError 拒絕 promise 作為原因。
  2. 如果 x 是一個 promise,則採用其狀態 [3.4]:
    1. 如果 x 是待定狀態,promise 必須保持待定,直到 x 被實現或拒絕。
    2. 如果 x 被實現,使用相同的值實現 promise
    3. 如果 x 被拒絕,使用相同的原因拒絕 promise
  3. 否則,如果 x 是一個物件或函式:
    1. then 設定為 x.then。[3.5]
    2. 如果檢索屬性 x.then 時丟擲異常 e,則以 e 作為原因拒絕 promise
    3. 如果 then 是一個函式,則以 x 作為 this 呼叫它,第一個引數為 resolvePromise,第二個引數為 rejectPromise,其中:
      1. 如果 resolvePromise 被呼叫並傳入值 y,則執行 [[Resolve]](promise, y)
      2. 如果 rejectPromise 被呼叫並傳入原因 r,則以 r 拒絕 promise
      3. 如果同時呼叫了 resolvePromiserejectPromise,或對同一引數進行了多次呼叫,第一次呼叫優先,後續呼叫將被忽略。
      4. 如果呼叫 then 時丟擲異常 e
        1. 如果 resolvePromiserejectPromise 已被呼叫,則忽略該異常。
        2. 否則,以 e 作為原因拒絕 promise
    4. 如果 then 不是一個函式,則用 x 來實現 promise
  4. 如果 x 既不是物件也不是函式,則用 x 來實現 promise

如果一個 promise 被一個迴圈的 thenable 鏈中的物件解決,而 [[Resolve]](promise, thenable) 的遞迴性質使得它被再次呼叫,按照上述演算法將會導致無限遞迴。雖然演算法並不強制要求檢測這種遞迴,但我們鼓勵實現者可以這樣做。如果檢測到存在迴圈,則應以一個語義清晰的 TypeError 拒絕 promise。[3.6]

3 引注

  1. 這裡的“平臺程式碼”指的是引擎、環境和 promise 的實現程式碼。實際上,這一要求確保 onFulfilledonRejected 在呼叫 then 的事件迴圈轉之後非同步執行,並且在一個新的呼叫棧中。這可以透過“宏任務”機制(如 setTimeoutsetImmediate)或“微任務”機制(如 MutationObserverprocess.nextTick)來實現。由於 promise 實現被視為平臺程式碼,它本身可能包含一個任務排程佇列或“跳板”,用於呼叫處理程式。

  2. 也就是說,在嚴格模式下,this 的值將是 undefined;在非嚴格模式下,this 將指向全域性物件。

  3. 實現可以允許 promise2 === promise1,前提是該實現滿足所有要求。每個實現應記錄是否可以產生 promise2 === promise1 以及在什麼條件下可以實現。

  4. 通常,只有當 x 來自當前實現時,才能確定 x 是一個真正的 promise。這一條款允許使用特定於實現的方法來採用已知符合標準的 promises 的狀態。

  5. 這個過程首先儲存對 x.then 的引用,然後測試該引用,最後呼叫該引用,避免了對 x.then 屬性的多次訪問。這種預防措施對於確保在訪問器屬性的情況下保持一致性非常重要,因為該屬性的值可能在多次檢索之間發生變化。

  6. 實現不應對 thenable 鏈的深度設定任意限制,並假設超出該限制的遞迴將是無限的。只有真正的迴圈才應導致 TypeError;如果遇到無限的不同 thenable 鏈,則無限遞迴是正確的行為。

相關文章