Promise探討

發表於2019-01-02

作者 | 周浪

一、前言

大家都知道JavaScript一大特點就是單執行緒,為了不阻塞主執行緒,有些耗時操作(比如ajax)必須放在任務佇列中非同步執行。傳統的非同步程式設計解決方案之一回撥,很容易產生臭名昭著的回撥地獄問題。

雖然回撥地獄可以通過減少巢狀、模組化等方式來解決,但我們有更好的方案可以採取,那就是 Promise

二、含義

Promise 是一個物件,儲存著非同步操作的結果,在非同步操作結束後,會變更 Promise 的狀態,然後呼叫註冊在 then 方法上回撥函式。 ES6 原生提供了 Promise 物件,統一用法(具體可參考阮一峰的《ES6入門》

三、實現

Promise 的使用想必大家都很熟練,可是究其內部原理,在這之前,我一直是一知半解。本著知其然,也要知其所以然的目的,開始對 Promise 的實現產生了興趣。

眾所周知, Promise 是對 Promises/A+ 規範的一種實現,那我們首先得了解規範, 詳情請看Promise/A+規範(https://promisesaplus.com/),個人github上有對應的中文翻譯README.md

promise建構函式

規範沒有指明如何書寫建構函式,那就參考下 ES6 的構造方式

Promise 建構函式接受一個函式作為引數,該函式的兩個引數分別是 resolve 和 reject

resolve 函式的作用是將 Promise 物件的狀態從 pending 變為 fulfilled ,在非同步操作成功時呼叫,並將非同步操作的結果,作為引數傳遞給註冊在 then 方法上的回撥函式(then方法的第一個引數); reject 函式的作用是將 Promise 物件的狀態從 pending 變為 rejected ,在非同步操作失敗時呼叫,並將非同步操作報出的錯誤,作為引數傳遞給註冊在 then 方法上的回撥函式(then方法的第二個引數)

所以我們要實現的 promise (小寫以便區分ES6的 Promise )建構函式大體如下:

規範2.2.6中明確指明 then 方法可以被同一個 promise 物件呼叫,所以這裡需要用一個陣列 onFulfilledCb 來儲存then方法中註冊的回撥

這裡我們執行 resolve reject 內部程式碼使用setTimeout,是為了確保 then 方法上註冊的回撥能非同步執行(規範3.1)

then方法

promise 例項具有 then 方法,也就是說, then 方法是定義在原型物件 promise.prototype 上的。它的作用是為 promise 例項新增狀態改變時的回撥函式。

規範2.2 promise 必須提供一個 then 方法 promise.then(onFulfilled,onRejected) 規範2.2.7 then 方法必須返回一個新的promise

閱讀理解規範2.1和2.2,我們也很容易對then方法進行實現:

重點在於對 onFulfilled 、 onRejected 函式的返回值x如何處理,規範中提到一個概念叫 PromiseResolutionProcedure ,這裡我們就叫做Promise解決過程

Promise 解決過程是一個抽象的操作,需要輸入一個 promise 和一個值,我們表示為 [[Resolve]](promise,x),如果 x 有 then 方法且看上去像一個 Promise ,解決程式即嘗試使 promise 接受 x 的狀態;否則用 x的值來執行 promise

promise解決過程

對照規範2.3,我們再來實現 promise resolution , promise resolution 針對x的型別做了各種處理:如果 promise 和 x 指向同一物件,以 TypeError 為 reason 拒絕執行 promise、如果 x 為 promise ,則使 promise 接受 x 的狀態、如果 x 為物件或者函式,判斷 x.then 是否是函式、 如果 x 不為物件或者函式,以 x 為引數執行 promise(resolve和reject引數攜帶promise2的作用域,方便在x狀態變更後去更改promise2的狀態)

完整程式碼可檢視stage-4(https://github.com/zhoulang27426405/learn-promise/blob/master/stage-4/promise-4.js)

思考

以上,基本實現了一個簡易版的 promise ,說白了,就是對 Promises/A+ 規範的一個翻譯,將規範翻譯成程式碼。因為大家的實現都是基於這個規範,所以不同的 promise 實現之間能夠共存(不得不說制定規範的人才是最厲害的)

至於 ES6 的 finally 、 all 等常用方法,規範雖然沒有制定,但是藉助 then 方法,我們實現起來也很方便stage-5(https://github.com/zhoulang27426405/learn-promise/tree/master/stage-5)

ES7 的 Async/Await 也是基於 promise 來實現的,可以理解成 async 函式會隱式地返回一個 Promise , await 後面的執行程式碼放到 then 方法中

更深層次的思考,你需要理解規範中每一條制定的意義,比如為什麼then方法不像jQuery那樣返回this而是要重新返回一個新的promise物件(如果then返回了this,那麼promise2就和promise1的狀態同步,promise1狀態變更後,promise2就沒辦法接受後面非同步操作進行的狀態變更)、 promise解決過程 中為什麼要規定 promise2 和 x 不能指向同一物件(防止迴圈引用)

promise的弊端

promise徹底解決了callback hell,但也存在以下一些問題

總結

支援 promise 的庫有很多,現在主流的瀏覽器也都原生支援 promise 了,而且還有更好用的 Async/Await 。之所以還要花精力去寫這篇文章,道理很簡單,就是想對規範有一個更深的理解,希望看到這裡的同學同樣也能有所收穫。

相關文章