Promise拆解計劃:由規範深入,從原理淺出

發表於2024-02-20

大家好,歡迎來到前端研習圈的今日分享。上期跟大家淺析了為什麼要了解 Promises/A+ 規範

今天我們就來拆解一下 Promises/A+,從規範入手,重塑對 Promise 的理解, 接下來直接進入正題~

什麼是 Promise

首先,我們看一下規範中如何定義 Promise

可以看到,規範中對 Promise 的描述是一個非同步操作的最終結果。我們可以透過 then 方法註冊回撥來接收它的最終的 value 或者失敗的 reason

這也是為什麼在很多有關 Promise 的介紹中把它稱為 狀態機 的原因。由此可見,規範也並非處處都是晦澀難懂的黑話,定義還是非常簡潔明瞭的。

開放的標準

同時 A+ 規範還是很開放的一套標準,在前言中,有這樣一段描述

規範並沒有約束 建立 一個 Promise 的方式,也沒有限制將一個 Promise 的狀態修改為 成功 或者 失敗 的方式,而是選擇聚焦於提供一個可互用的 then 方法。

如何理解這個 可互用 呢,其實也很簡單,假設有兩個基於 Promises/A+ 實現 AB,針對 A 建立的 promise 的 then 處理邏輯,放在 B 上也同樣適用,這也就意味著,這套規範會為 then 增加很多約束條件,以實現 可互用

實際上也確實如此,A+ 中大部分的條例其實都是在規定 then 方法應該如何實現

約定俗成的術語

在進入 A+ 的為 Promise 制定的需求列表之前,我們先了解一下其中的 術語

關於 promisethenable 的描述大家可能會有點疑惑,為什麼都是一個具備 then 方法的物件(函式本質上也是物件),卻要叫兩個名字? 其實很好解釋,promise 是官方實現的叫法,而 thenable 是非官方實現的統稱(社群實現的叫法)

value, reason, exception 規範中說得很明確,這裡就不做多餘的闡述了

狀態的處理細則

規範中規定了一個 promise 的狀態必須為 pendingfulfilled,rejected 三者之一

處在 pending 時, 可以變為 fulfilled,rejected 兩者之一

變為 fulfilled 時,狀態不能再改變,同時必須產出 value,且 value 也不能被改變

變為 rejected 時,狀態不能再改變,同時必須產出 reason,且 reason 也不能被改變

說簡單一點,promise 的狀態只能被改變一次,要麼成功要麼失敗, 有點不成功便成仁的味道了。

then 方法

關於 then 描述中, 規定了它需要接收兩個引數

這裡onFulfilledonRejected 也就是我們日常說的 成功的回撥 和 失敗的回撥

約束條例

onFulfilledonRejected 不是函式的場景將直接被忽略


onFulfilled 是一個函式,它必須在 promise 變為 fulfilled 後呼叫,同時把 value 作為引數,且只能呼叫一次


onRejected 是一個函式,它必須在 promise 變為 rejected 後呼叫,同時把 reason 作為引數,且只能呼叫一次


onFulfilledonRejected 必須在平臺程式碼的執行上下文結束之後呼叫。

這句話怎麼理解呢,假設在 executor 中同步執行了 resolvereject,成功/失敗的回撥也必須等同步邏輯全部執行完後,才能執行。這也就是為什麼 onFulfilledonRejected 會被放置在 micro task 中執行,而不是立即執行的原因

const p = new Promise((resolve, reject) => {
  resolve(3)
  console.log(1)
})
console.log(2)
p.then((value) => {
  console.log(value)
})
// 輸出 1 2 3

onFulfilledonRejected 必須作為普通函式呼叫,也就是說不考慮內部 this 的指向


then 在同一個 promise 上被呼叫多次,所註冊的回撥需要在 promise 變為 fulfilledrejected 後,按照註冊的順序執行


then 方法的返回值,必須是一個新的 promise(promise2),這也就是 promise 能夠被 鏈式呼叫 的原因

onFulfilledonRejected 的返回值 x,需要交給一個名為 Promise Resolution Procedure 的流程去處理,主要是考慮 x 還是一個 promise 的場景,因此需要設計一個遞迴流程來處理,這個流程的條例在下文會講

onFulfilledonRejected 如果執行報錯,丟擲異常 e,那麼 新返回的promise2 的狀態將變為 rejected

當 promise1 狀態變為 fulfilledonFulfilled 不是一個函式時,promise2 將以相同的 value 變為 fulfilled

當 promise1 狀態變為 rejectedonRejected 不是一個函式時,promise2 將以相同的 reason 變為 rejected

Promise Resolution Procedure

最後就是上文中提及的 Promise Resolution Procedure 流程,這是一個抽象出來的概念並非某個具體實現。它會接收一個兩個引數,一個 promise 和一個 x

這裡規範中提到,當 x 是一個 thenable 時, 會嘗試讓 promise 使用 x 最終的狀態

看個例子吧

結合例子就不難發現,這樣做的意義是保證 then 方法返回的 p2 的狀態是由 p1onFulfilledonRejected 的返回值的狀態決定的。

接下來我們來看整體流程的條例

promisex 的引用是同一個物件,則將 promise 置為 rejected 並丟擲 TypeError 作為失敗的 reason

也就是如下場景


x 是一個 promise,則會根據其狀態做不同的處理

如果 x 處於 pending,那麼 promise 需要等 x 成功或失敗後再同步其狀態;如果 x 已經成功或失敗,那麼 promise 則直接同步其狀態


x 是一個普通物件或者函式時,這部分場景是 A+ 中最複雜的部分,考慮到單純用文字描述可能會很抽象,這部分將放置在下期的 原始碼實現篇 中著重拆解

有能力的同學可以嘗試直接原文


x 既不是一個普通物件也不是函式時,promisex 都將直接被置位 fulfilled


以上就是 Promises/A+ 規範中全部條例了

結尾

-> Promises/A+ 原文

經過對規範的拆解後,相信大家對 promise 的認識又有了不小的提升。不過還保留了一個大坑等著下期來補(條例2.3.3)。下期我們將從 規範 -> 實現 來切實的感受 promise 的工作流程

最後希望大家每天進步一點點,我們下期見~

相關文章