[JS]自己動手寫個Promise

張庭瑋
9 min readJun 11, 2020

--

Promise為JavaScript的一個內建物件,為非同步操作提供了一個比起無限巢狀callbacks較易閱讀的解決方案,ES7的async/await也是以此為基礎發展,可謂是ES6的一個相當重要的語法

首先先看一下MDN的介紹來了解原理

今天要介紹以下物件或函式的基本寫法

  1. Promise的基本實現
  2. then()的基本實現
  3. then()的非同步操作
  4. then()的鏈式調用
  5. then()的穿透
  6. Promise的resolve().reject()的基本實現
  7. Promise.prototype.catch()的基本實現
  8. Promise的all().race()的基本實現

Promise的基本實現

依照MDN所說

一個 Promise 物件處於以下幾種狀態:

  • 擱置(pending):初始狀態,不是 fulfilled 與 rejected。
  • 實現(fulfilled):表示操作成功地完成。
  • 拒絕(rejected):表示操作失敗了。

先定義狀態:

executor為一個依序接收兩個參數的函式:resolvereject(實現及拒絕回呼函式)。在 Promise 實作中,executor 函式在傳入參數 resolvereject 後會立刻執行(executor 函式會在 Promise 建構式回傳 Promise 物件前被執行)…

所以Promise應具備:value:儲存成功的值,reason:儲存失敗的理由,resolve函式:處理成功的結果,reject函式:處理Error,以及紀錄狀態的status

根據以上所述來試著寫建構函式

resolve跟reject就是把取到的值存進來並且更改狀態
別忘了處理異常

then()的基本實現

一個處於擱置狀態的 promise 能以一個值被實現(fulfilled),或是以一個原因或錯誤而被拒絕(rejected)。當上述任一狀態轉換發生時,那些透過 then 方法所繫結(associated)的處理函式列隊就會依序被調用……

由於 Promise.prototype.then() 以及 Promise.prototype.catch() 方法都回傳 promise…

then()是Promise的prototype上的方法

當狀態是成功時調用onFulfilled方法,失敗時則調用onRejected方法

來稍微測試一下剛才寫的東西

沒有問題

然而以上還未接觸到Promise的核心:非同步操作

then()的非同步操作

那些透過 then 方法所繫結(associated)的處理函式列隊就會依序被調用……

這裡是說要先把callbacks存起來,等到非同步執行完畢後再依序調用

在Promise中新增兩個陣列來儲存callbacks
並且在成功或失敗後執行一遍callbacks
then中判斷:在Promise的狀態仍處於pending時儲存callbacks

接著來測試

成功取得資料

then()的鏈式調用

接著是promise.then(…).then(…)….這種鏈式調用的實現,這時then必須return一個新的Promise

(因為Promise的prototype上才有then())

(還有Promise狀態不可以再次改變,所以不能return原本的Promise)

先把原本then()中的內容包進Promise裡
設一個變數origin來取得回調函數的結果

取得後要先做些處理(else裡的改寫法也跟if裡的相同)

將處理的函數命名為resolvePromise

首先要判斷返回的promise跟current是不是一樣的東西,如果是一樣的東西會造成無限循環調用,這時要直接以reject(Error)的方式處理

再來看returned的型態是不是object或function(只有這兩種型態才有可能內藏then()函式,這裡有種稱呼法:thenable),如果不是的話,就直接將結果resolve,如果是的話往下進行:

if裡面主要目的就是以循環調用resolvePromise的方式把promise全部跑完(拆掉?),其中調用then時要特別注意需要用到call來指定this,如果不使用call的情況下直接調用then(),這時this的指向就會是window(全域),而非我們剛剛處理到一半的Promise,這樣就會使得我們無法獲取該Promise的狀態

這裡還有一:resolve跟reject只能擇一被調用,如果都被調用或多次調用則只先跑最初被調用的,於是我們還要設個變數isCalled用來判斷是否已被調用

方法是在可能被調用的地方都先設個if判斷isCalled的值,如果是true直接return,而當第一次被調用時則將isCalled的值變更為true,這裡要做這種檢查的地方有三個:

第一跟第二自然是then.call(…)裡的resolve跟reject

第三個是try調用之後catch裡也有可能有這種多次調用的問題

這時當然還是得測試一下

成功!

這邊還有一種用法

then()的穿透

p.then().then(…).then().then(…)

跑出來的東西跟沒使用無參數的then()相同,也就是沒給參數的then執行時東西會直接穿透過去

做法就是then被調用時判斷是否有傳入失敗或成功的callbacks,沒有的話就傳入上回失敗或成功的值

來幾個空的then()試試看
結果跟原來相同

這裡還有一個小東西

2.2.4 onFulfilled or onRejected must not be called until the execution context stack contains only platform code.

如果是同步執行的話,用Promise時還得幫then()考慮ㄧ下,前面有沒有非同步事件,執行完了嗎?

這時為了確保正確的執行順序,then()要以非同步的方式執行

簡單的做法就是使用setTimeout

Promise的resolve().reject()的基本實現

所以寫起來就是

Promise.prototype.catch()的基本實現

MDN寫得滿清楚的,直接寫起來就是

Promise的all().race()的基本實現

還有比較常用的all()及racr()

Promise.all()

先設個空陣列進行儲存的動作

當資料儲存的次數跟傳進來陣列長度一樣時,才可以將結果resolve

Promise.race()

先跑先贏的感覺

--

--

No responses yet