前段時間太忙,隔了快一個月沒寫博客,可是 Promise
其實很早以前就已經總結了一波如何實現,可是那個時候純粹是爲了實現而實現,沒有去細品其中的一些巧妙設計,直到最近在進行函數式編程相關的知識學習時,無心中在查閱資料的時候發現,Promise
和 Functor
竟然有着千絲萬縷的關係,這讓我決定要從新審視一下本身對 Promise
的認知,因而便有了這篇「老酒新裝」的博客。javascript
想要徹底閱讀並理解這篇博客,我粗略地估算了一下,大概須要如下的一些前置知識,不瞭解的同窗能夠自行先去學習一下:前端
ES6
和 Typescript
的基礎認知Functor
)和「函數是一等公民」思想Promise
咱們能夠將實現一個 Promise
比做蓋一棟樓,經過拆分每一步並解決、理解和記憶,達到很快就能理解它的實現的目的。java
Promise
的地基因爲咱們經常使用 Promise
配合 async/await
來實現異步編程,因此先回想一下如何最最基本地使用 Promise
git
new Promise(resolve => {...}) .then(onFulfilled) .then(onFulfilled)
根據上面咱們回憶中的最最基本的 Promise
咱們能夠寫出如下實現:github
class Promise { constructor(executor) { const resolve = () => {} executor(resolve) } then = onFulfilled => new Promise(...) }
好的,那咱們的第一步到這裏就結束了,是否是十分簡單,徹底不須要什麼成本就能理解並記憶下來。shell
這一步,咱們開始往裏加東西了。對於加的東西,咱們也必須瞭解是什麼,因此先來看下兩個原材料的基本概念。npm
因爲在 前置知識 裏提到,相信你們已經對它有所瞭解,一個基本的函子咱們能夠寫成如下實現:編程
class Functor { static of = value => new Functor(this.value) constructor(value) { this.value = value } map = fn => Functor.of(fn(this.value)) }
爲了方便映射到 Promise
的實現中,改成如下寫法:segmentfault
class Functor { constructor(value) { this.value = value } map = fn => new Functor(fn(this.value)) }
而後結合到函子的一些特性:設計模式
const plus = x + y => x + y const plusOne = x => plus(x, 1) new Functor(100) .map(plusOne) .map(plusOne) // { value: 102 }
這個是時候再發揮咱們從小被培養的找規律能力,咱們發現:
經過以上兩點能夠獲得一個結論,之因此引入函子,能夠解決鏈式調用的問題,可是光有一個函子不夠呀,函子只能實現同步的鏈式調用,這時候另一個原材料觀察者模式就出場了。
先看一個簡單的觀察者模式實現:
class Observer { constructor() { this.callbacks = [] this.notify = value => { this.callbacks.forEach(observe => observe(value)) } } subscribe = observer => { this.callbacks.push(observer) } }
這時候聰明的人一下就發現了,這個 notify
和 subscribe
不就是 resolve
和 then
嘛!
俺のターン!ドロー!魔法発動!
ターンエンド!
class Promise { constructor(executor) { this.value = undefined this.callbacks = [] // 至關於 notify const resolve = value => { this.value = value this.callbacks.forEach(callback => callback()) } executor(resolve) } // 至關於 subscribe 或 map then = onFulfilled => new Promise(resolve => { this.callbacks.push(() => resolve(onFulfilled(this.value))) }) }
融合後的初級 Promise
已經具備異步鏈式調用的能力了好比:
const promise = new Promise(resolve => { setTimeout(() => { resolve(100) }, 500) }) .map(plusOne) .map(plusOne) // { value: 102 }
可是當咱們進行一些騷操做時,依然會出問題:
const promise = new Promise(resolve => { setTimeout(() => { resolve(100) resolve(1000) }, 500) }) .map(plusOne) .map(plusOne) // { value: 1002 }
爲了解決這個問題,咱們還須要一個原材料狀態。
篇幅有限,這一部分更細緻的轉換過程,個人 repo 都有記錄。
衆所周知,<span style="text-decoration-line: line-through">「青眼究極龍鬚要三條青眼白龍」</span>,爲了解決上一部分留下的問題,這一部分,須要給 Promise
加入狀態這個原材料。
class Promise { static PENDING = 'PENDING' static FULFILLED = 'FULFILLED' constructor(executor) { this.value = undefined this.callbacks = [] this.status = Promise.PENDING // 一系列操做(狀態的改變,成功回調的執行) const resolve = value => { // 只有處於 pending 狀態的 promise 能調用 resolve if (this.status === Promise.PENDING) { // resolve 調用後,status 轉爲 fulfilled this.status = Promise.FULFILLED // 儲存 fulfilled 的終值 this.value = value // 一旦 resolve 執行,調用儲存在回調數組裏的回調 this.callbacks.forEach(callback => callback()) } } executor(resolve) } then = onFulfilled => new Promise(resolve => { // 當 status 爲執行態(Fulfilled)時 if (this.status === Promise.FULFILLED) { resolve(onFulfilled(this.value)) } // 當 status 爲 Pending 時 if (this.status === Promise.PENDING) { // 將 onFulfilled 存入回調數組 this.callbacks.push(() => resolve(onFulfilled(this.value))) } }) }
至此,經過三大原材料構建出的 Promise
就完成了,固然,還有不少功能沒有實現,<span style="text-decoration-line: line-through">魯迅曾經說過:</span>「要站在巨人的肩膀上看問題。」,下一步,就須要 Promise/A+
規範來來幫助咱們實現一個具備完整功能的 Promise
。
Promise/A+
規範劍來! Promise/A+ 規範,接下來的操做,須要跟着它一步一步進行。
其實這一步不用規範咱們也知道,Promise
擁有的終態有 fulfilled
和 rejected
兩種,因此要把剩下的 rejected
以及一些相關操做給補上。
class Promise { ...... static REJECTED = 'REJECTED' constructor(executor) { this.value = undefined this.reason = undefined this.onFulfilledCallbacks = [] this.onRejectedCallbacks = [] this.status = PromiseFunctorWithTwoStatus.PENDING // 成功後的一系列操做(狀態的改變,成功回調的執行) const resolve = value => { ...... } // 失敗後的一系列操做(狀態的改變,失敗回調的執行) const reject = reason => { // 只有處於 pending 狀態的 promise 能調用 resolve if (this.status === Promise.PENDING) { // reject 調用後,status 轉爲 rejected this.status = Promise.REJECTED // 儲存 rejected 的拒因 this.reason = reason // 一旦 reject 執行,調用儲存在失敗回調數組裏的回調 this.onRejectedCallbacks.forEach(onRejected => onRejected()) } } executor(resolve, reject) } then = (onFulfilled, onRejected) => new Promise(resolve => { // 當 status 爲執行態(Fulfilled)時 ...... // 當 status 爲拒絕態(Rejected)時 if (this.status === PromiseFunctorWithTwoStatus.REJECTED) { reject(onRejected(this.reason)) } // 當 status 爲 Pending 時 if (this.status === Promise.PENDING) { // 將 onFulfilled 存入回調數組 this.onFulfilledCallbacks.push(() => resolve(onFulfilled(this.value))) // 將 onRejected 存入失敗回調數組 this.onRejectedCallbacks.push(() => reject(onRejected(this.reason))) } }) }
resolvePromise
方法實現解決過程Promise 解決過程是一個抽象的操做,其需輸入一個 promise 和一個值,咱們表示爲 [[Resolve]](promise, x),若是 x 有 then 方法且看上去像一個 Promise ,解決程序即嘗試使 promise 接受 x 的狀態;不然其用 x 的值來執行 promise 。這種 thenable 的特性使得 Promise 的實現更具備通用性:只要其暴露出一個遵循 Promise/A+ 協議的 then 方法便可;這同時也使遵循 Promise/A+ 規範的實現能夠與那些不太規範但可用的實現能良好共存。
根據規範的描述,咱們依照他給的實現步驟,寫出代碼實現:
class Promise { ...... static resolvePromise = (anotherPromise, x, resolve, reject) => { // 若是 onFulfilled 或者 onRejected 返回一個值 x ,則運行下面的 Promise 解決過程:[[Resolve]](promise2, x) // 運行 [[Resolve]](promise, x) 需遵循如下步驟: // 若是 promise 和 x 指向同一對象,以 TypeError 爲拒因拒絕執行 promise 以防止循環引用 if (anotherPromise === x) { return reject(new TypeError('Chaining cycle detected for promise')) } // 若是 x 爲 Promise ,則使 promise 接受 x 的狀態 if (x instanceof Promise) { x.then( // 若是 x 處於執行態,用相同的值執行 promise value => { return Promise.resolvePromise(anotherPromise, value, resolve, reject) }, // 若是 x 處於拒絕態,用相同的拒因拒絕 promise reason => { return reject(reason) } ) // 若是 x 爲對象或者函數 } else if (x !== null && (typeof x === 'object' || typeof x === 'function')) { let called = false try { // 把 x.then 賦值給 then(這步咱們先是存儲了一個指向 x.then 的引用,而後測試並調用該引用,以免屢次訪問 x.then 屬性。這種預防措施確保了該屬性的一致性,由於其值可能在檢索調用時被改變。) const then = x.then // 若是 then 是函數,將 x 做爲函數的做用域 this 調用之。傳遞兩個回調函數做爲參數, if (typeof then === 'function') { then.call( x, // 第一個參數叫作 resolvePromise , value => { // 若是 resolvePromise 和 rejectPromise 均被調用,或者被同一參數調用了屢次,則優先採用首次調用並忽略剩下的調用 if (called) { return } called = true // 若是 resolvePromise 以值 y 爲參數被調用,則運行 [[Resolve]](promise, y) return Promise.resolvePromise( anotherPromise, value, resolve, reject ) }, // 第二個參數叫作 rejectPromise reason => { // 若是 resolvePromise 和 rejectPromise 均被調用,或者被同一參數調用了屢次,則優先採用首次調用並忽略剩下的調用 if (called) { return } called = true // 若是 rejectPromise 以拒因 r 爲參數被調用,則以拒因 r 拒絕 promise return reject(reason) } ) } else { //若是 then 不是函數,以 x 爲參數執行 promise return resolve(x) } } catch (error) { // 若是調用 then 方法拋出了異常 e, 若是 resolvePromise 或 rejectPromise 已經被調用,則忽略之 if (called) { return } called = true // 若是取 x.then 的值時拋出錯誤 e ,則以 e 爲拒因拒絕 promise return reject(error) } } else { // 若是 x 不爲對象或者函數,以 x 爲參數執行 promise return resolve(x) } } ...... }
同時,咱們要對以前的 Promise
裏的 resolve
方法進行改造:
class Promise { ...... constructor(executor) { ...... // 成功後的一系列操做(狀態的改變,成功回調的執行) const resolve = x => { const __resolve = value => { // 只有處於 pending 狀態的 promise 能調用 resolve if (this.status === Promise.PENDING) { // resolve 調用後,status 轉爲 fulfilled this.status = Promise.FULFILLED // 儲存 fulfilled 的終值 this.value = value // 一旦 resolve 執行,調用儲存在成功回調數組裏的回調 this.onFulfilledCallbacks.forEach(onFulfilled => onFulfilled()) } } return Promise.resolvePromise.call(this, this, x, __resolve, reject) } ...... } ...... }
class Promise { ...... then = (onFulfilled, onRejected) => { // then 方法必須返回一個 promise 對象 const anotherPromise = new Promise((resolve, reject) => { // 封裝處理鏈式調用的方法 const handle = (fn, argv) => { // 確保 onFulfilled 和 onRejected 方法異步執行 setTimeout(() => { try { const x = fn(argv) return Promise.resolvePromise(anotherPromise, x, resolve, reject) } catch (error) { return reject(error) } }) } // 當 status 爲執行態(Fulfilled)時 if (this.status === Promise.FULFILLED) { // 則執行 onFulfilled,value 做爲第一個參數 handle(onFulfilled, this.value) } // 當 status 爲拒絕態(Rejected)時 if (this.status === Promise.REJECTED) { // 則執行 onRejected,reason 做爲第一個參數 handle(onRejected, this.reason) } // 當 status 爲 Pending 時 if (this.status === Promise.PENDING) { // 將 onFulfilled 存入成功回調數組 this.onFulfilledCallbacks.push(() => { handle(onFulfilled, this.value) }) // 將 onRejected 存入失敗回調數組 this.onRejectedCallbacks.push(() => { handle(onRejected, this.reason) }) } }) return anotherPromise } ...... }
Promise
的主體已經寫好了,接下來要實現其餘的一些輔助方法來完善它。
catch = onRejected => { return this.then(null, onRejected) }
finally = fn => { return this.then( value => { setTimeout(fn) return value }, reason => { setTimeout(fn) throw reason } ) }
static resolve = value => new Promise((resolve, reject) => resolve(value))
static reject = reason => new Promise((resolve, reject) => reject(reason))
static all = promises => { if (!isArrayLikeObject(promises)) { throw new TypeError( `${ typeof promises === 'undefined' ? '' : typeof promises } ${promises} is not iterable (cannot read property Symbol(Symbol.iterator))` ) } // 實現的 promise 基於 macroTask 的 setTimeout 實現,須要 async/await 調節執行順序 // 原生的 promise 基於 microTask 實現,執行順序是正確的,不須要 async/await return new Promise(async (resolve, reject) => { const result = [] for (const promise of promises) { await Promise.resolve(promise).then(resolvePromise, rejectPromise) } return resolve(result) function resolvePromise(value) { if (value instanceof Promise) { value.then(resolvePromise, rejectPromise) } else { result.push(value) } } function rejectPromise(reason) { return reject(reason) } }) }
static race = promises => { if (!isArrayLikeObject(promises)) { throw new TypeError( `${ typeof promises === 'undefined' ? '' : typeof promises } ${promises} is not iterable (cannot read property Symbol(Symbol.iterator))` ) } return new Promise((resolve, reject) => { for (const promise of promises) { Promise.resolve(promise).then( value => resolve(value), reason => reject(reason) ) } }) }
這一部分基本上屬於修修補補了,增強 Promise
的健壯性
executor
constructor(executor) { // 參數校驗 if (typeof executor !== 'function') { throw new TypeError(`Promise resolver ${executor} is not a function`) } }
Maybe函子
的思想,校驗 onFulfilled
和 onRejected
then = (onFulfilled, onRejected) => { // 若是 onFulfilled 不是函數,其必須被「忽略」 onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value // 若是 onFulfilled 不是函數,其必須被「忽略」 onRejected = typeof onRejected === 'function' ? onRejected : error => { throw error } }
Typescript
風格這一部分就不寫上來了,repo 裏有記錄。
Promise/A+
規範咱們經過一個庫來檢測寫好的 Promise
:
添加須要的膠水代碼:
class Promise { ...... static defer = () => { let dfd: any = {} dfd.promise = new Promise((resolve, reject) => { dfd.resolve = resolve dfd.reject = reject }) return dfd } static deferred = Promise.defer ...... }
npm i promises-aplus-tests -D npx promises-aplus-tests promise.js
最近在翻閱資料的過程當中,真實地感悟到什麼是「溫故而知新」和「前端的知識雖然雜可是都有聯繫」。原本 Promise
的實現都被寫爛了,可是在學習函數式編程的時候竟然又繞回來了,這種感受實在奇妙,讓人不由佩服第一個產生 Promise
想法的人。Promise
將 函子(functor)
和 觀察者模式
相結合,加以 狀態
、Promise 的解決過程
進行改造,最終得以實現一個異步解決方案。
篇幅有限,不免一些錯誤,歡迎探討和指教~
附一個 GitHub 完整的 repo 地址:https://github.com/LazyDuke/ts-promise-from-functor-observer
這是一個系列,系列文章: