JavaScript Promise 實作理解筆記

如何實作JavaScript Promise?

本篇文章是我閱讀了A+ Promise implementing的筆記與心得。因為官方講解其實用語很精煉,所以我決定用我自己的話寫一篇容易看得懂的筆記。有任何錯誤歡迎留言指正。
PS: 我在本篇文章中交替使用resolve與議決這兩個詞彙。

建立物件內部變數

建立轉換狀態的內部方法fullfill和reject

建立更高階轉換狀態的方法resolve

getThen

判斷傳入的值是不是promise,如果是的話回傳該promise的then方法,可以注意到檢查方式很鬆散,只是檢查有沒有then方法而已,這種方式可以讓多個不同的promise library彼此相容。

doResolve

實際進行議決,doResolve 有責任確保傳入的resolve和reject這兩個參數只有其中一個會被呼叫一次
注意doResolve的工作,他會以傳入的fn進行議決,然後再根據議決結果呼叫傳入的onFulfilled或onRejected。並且使用了一個內部變數done來確保onFulfilled或onRejected只會被呼叫一次。

仔細觀察可以注意到,fn吃兩個參數,一個是成功時的callback,另一個是失敗時的callback,正好對應到Promise建立時的syntax
new Promise(function(resolve, reject) { ... });

建立Promise建構式

整個promise基本設定完成後,執行最後一行doResolve,直接對new Promise(xxx)的xxx進行議決,要是議決成功就執行resolve,議決失敗就執行reject。

解釋

為什麼resolve要搞這麼複雜,還要透過doResolve來解決?因為一個promise被議決時有下列兩種情況:

如果該promise發現他必須再resolve另一個promise(他具有then方法),那麼就必須繼續取得該promise議決的結果。取得結果的方法是呼叫該promise的then,一旦呼叫then後會有三種情況,沒事、呼叫onFulfilled callback,或是呼叫onRejected callback。
doResolve吃三個參數,fn(要議決的內容), onFulfilled(成功時的callback), onRejected(失敗時的callback),因此我們可以把要fn訂成該promise的then,也就是doResolve(then.bind(result), resolve, reject)。
bind會把執行then時的this綁定到該promise上,因此看起來就像呼叫了該promise的then,如果成功的話就繼續議決(resolve),如果失敗的話就否決(reject)

觀察Promise狀態

我們已經完成所有基本的工作了,現在唯一的問題是,我們沒辦法知道該promise到底有沒有乖乖把任務完成,因此我們需要.then來回報狀態。
但我們先來實作.done吧,因為.done比.then簡單一點

promise.done(onFulfilled, onRejected)

首先我們有幾個需求

  1. 只有onFulfilled或onRejected其中之一會被呼叫
  2. 只會被呼叫一次
  3. 他不會立刻被呼叫,而是會在done return之後之後才會被呼叫(非同步)。
  4. 不管我們的promise在call .done之前被議決或是.done之後,他就是會被呼叫

.done透過setTimeout來達成非同步的效果,在next Tick之後才根據狀態執行handle看看(晚點再講為什麼要這麼做),然後根據狀態決定要先等待還是進行處理。如此一來,就可以透過傳入.done的callback來讓promise根據狀態決定是否執行任務了。
搞懂.done後再來就是大魔王.then了

Promise.then(onFulfilled, onRejected)

可以看到我們用了一個很漂亮的作法來實作.then,那就.then會回傳一個新的Promise。如此一來你就可以使用Promise Chain來串接).then(cb).then(cb).then(cb)
這裡超級精彩的:.then回傳了一個新的Promise,而這個Promise所答應的事情是「原本Promise的完成(done)」
光講不清楚,我們寫一段簡單的code就知道什麼意思了

呼叫.then()後, .then()會回傳一個新的Promise,這個Promise會去呼叫yeePromise內部的.done(),而.done會先檢查yeePromise的狀態,發現是Pending,就先把handler放在handlers裡頭,直到5秒後yeePromise被resolve了,他才會執行剛剛保存的handler,最後印出'yeeeee'

為什麼.done內要setTimeout(fn, 0)

這是很重要的問題。請看下列程式碼

你預期會發生什麼事?
如果Promise是非同步的話,答案會是A() -> B() -> query()
如果Promise是同步的話,答案會是A()->query()->B()
為了避免讓程式設計師混淆,因此Promise的實作規格規定一定要是非同步的。

注意

另外請注意,.done並不是Promise/A+的實作標準規格,但大多數的標準Library會實作他。
以上就是我的筆記,希望這份筆記能夠讓你簡單的理解Promise是如何實作的。

reference

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *