前段時間太忙,隔了快一個月沒寫博客,可是 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
的實現中,改成如下寫法:設計模式
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 都有記錄。
衆所周知,「青眼究極龍鬚要三條青眼白龍」,爲了解決上一部分留下的問題,這一部分,須要給 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
就完成了,固然,還有不少功能沒有實現,魯迅曾經說過:「要站在巨人的肩膀上看問題。」,下一步,就須要 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 地址:github.com/LazyDuke/ts…
這是一個系列,系列文章: