Promise實現原理(附原始碼)

發表於2018-09-15

本篇文章主要在於探究 Promise 的實現原理,帶領大家一步一步實現一個 Promise , 不對其用法做說明,如果讀者還對Promise的用法不瞭解,可以檢視阮一峰老師的ES6 Promise教程

接下來,帶你一步一步實現一個 Promise

1. Promise 基本結構

建構函式Promise必須接受一個函式作為引數,我們稱該函式為handlehandle又包含resolvereject兩個引數,它們是兩個函式。

定義一個判斷一個變數是否為函式的方法,後面會用到

首先,我們定義一個名為 MyPromiseClass,它接受一個函式 handle 作為引數

再往下看

2. Promise 狀態和值

Promise 物件存在以下三種狀態:

  • Pending(進行中)
  • Fulfilled(已成功)
  • Rejected(已失敗)

狀態只能由 Pending 變為 Fulfilled 或由 Pending 變為 Rejected ,且狀態改變之後不會在發生變化,會一直保持這個狀態。

Promise的值是指狀態改變時傳遞給回撥函式的值

上文中handle函式包含 resolvereject 兩個引數,它們是兩個函式,可以用於改變 Promise 的狀態和傳入 Promise 的值

這裡 resolve 傳入的 "FULFILLED" 就是 Promise 的值

resolvereject

  • resolve : 將Promise物件的狀態從 Pending(進行中) 變為 Fulfilled(已成功)
  • reject : 將Promise物件的狀態從 Pending(進行中) 變為 Rejected(已失敗)
  • resolvereject 都可以傳入任意型別的值作為實參,表示 Promise 物件成功(Fulfilled)和失敗(Rejected)的值

瞭解了 Promise 的狀態和值,接下來,我們為 MyPromise 新增狀態屬性和值

首先定義三個常量,用於標記Promise物件的三種狀態

再為 MyPromise 新增狀態和值,並新增狀態改變的執行邏輯

這樣就實現了 Promise 狀態和值的改變。下面說一說 Promise 的核心: then 方法

3. Promisethen 方法

Promise 物件的 then 方法接受兩個引數:

引數可選

onFulfilledonRejected 都是可選引數。

  • 如果 onFulfilledonRejected 不是函式,其必須被忽略

onFulfilled 特性

如果 onFulfilled 是函式:

  • promise 狀態變為成功時必須被呼叫,其第一個引數為 promise 成功狀態傳入的值( resolve 執行時傳入的值)
  • promise 狀態改變前其不可被呼叫
  • 其呼叫次數不可超過一次

onRejected 特性

如果 onRejected 是函式:

  • promise 狀態變為失敗時必須被呼叫,其第一個引數為 promise 失敗狀態傳入的值( reject 執行時傳入的值)
  • promise 狀態改變前其不可被呼叫
  • 其呼叫次數不可超過一次

多次呼叫

then 方法可以被同一個 promise 物件呼叫多次

  • promise 成功狀態時,所有 onFulfilled 需按照其註冊順序依次回撥
  • promise 失敗狀態時,所有 onRejected 需按照其註冊順序依次回撥

返回

then 方法必須返回一個新的 promise 物件

因此 promise 支援鏈式呼叫

這裡涉及到 Promise 的執行規則,包括“值的傳遞”和“錯誤捕獲”機制:

1、如果 onFulfilled 或者 onRejected 返回一個值 x ,則執行下面的 Promise 解決過程:[[Resolve]](promise2, x)

  • x 不為 Promise ,則使 x 直接作為新返回的 Promise 物件的值, 即新的onFulfilled 或者 onRejected 函式的引數.
  • xPromise ,這時後一個回撥函式,就會等待該 Promise 物件(即 x )的狀態發生變化,才會被呼叫,並且新的 Promise 狀態和 x 的狀態相同。

下面的例子用於幫助理解:

2、如果 onFulfilled 或者onRejected 丟擲一個異常 e ,則 promise2 必須變為失敗(Rejected),並返回失敗的值 e,例如:

3、如果onFulfilled 不是函式且 promise1 狀態為成功(Fulfilled)promise2 必須變為成功(Fulfilled)並返回 promise1 成功的值,例如:

4、如果 onRejected 不是函式且 promise1 狀態為失敗(Rejected)promise2必須變為失敗(Rejected) 並返回 promise1 失敗的值,例如:

根據上面的規則,我們來為 完善 MyPromise

修改 constructor : 增加執行佇列

由於 then 方法支援多次呼叫,我們可以維護兩個陣列,將每次 then 方法註冊時的回撥函式新增到陣列中,等待執行

新增then方法

首先,then 返回一個新的 Promise 物件,並且需要將回撥函式加入到執行佇列中

那返回的新的 Promise 物件什麼時候改變狀態?改變為哪種狀態呢?

根據上文中 then 方法的規則,我們知道返回的新的 Promise 物件的狀態依賴於當前 then 方法回撥函式執行的情況以及返回值,例如 then 的引數是否為一個函式、回撥函式執行是否出錯、返回值是否為 Promise 物件。

我們來進一步完善 then 方法:

這一部分可能不太好理解,讀者需要結合上文中 then 方法的規則來細細的分析。

接著修改 _resolve_reject :依次執行佇列中的函式

resolvereject 方法執行時,我們依次提取成功或失敗任務佇列當中的函式開始執行,並清空佇列,從而實現 then 方法的多次呼叫,實現的程式碼如下:

這裡還有一種特殊的情況,就是當 resolve 方法傳入的引數為一個 Promise 物件時,則該 Promise 物件狀態決定當前 Promise 物件的狀態。

上面程式碼中,p1p2 都是 Promise 的例項,但是 p2resolve方法將 p1 作為引數,即一個非同步操作的結果是返回另一個非同步操作。

注意,這時 p1 的狀態就會傳遞給 p2,也就是說,p1 的狀態決定了 p2 的狀態。如果 p1 的狀態是Pending,那麼 p2 的回撥函式就會等待 p1 的狀態改變;如果 p1 的狀態已經是 Fulfilled 或者 Rejected,那麼 p2 的回撥函式將會立刻執行。

我們來修改_resolve來支援這樣的特性

這樣一個Promise就基本實現了,現在我們來加一些其它的方法

catch 方法

相當於呼叫 then 方法, 但只傳入 Rejected 狀態的回撥函式

靜態 resolve 方法

靜態 reject 方法

靜態 all 方法

靜態 race 方法

finally 方法

finally 方法用於指定不管 Promise 物件最後狀態如何,都會執行的操作

這樣一個完整的 Promsie 就實現了,大家對 Promise 的原理也有了解,可以讓我們在使用Promise的時候更加清晰明瞭。

完整程式碼如下

如果覺得還行的話,點個贊、收藏一下再走吧。

相關文章