手動實現一個 promise
規範
首先,讓我們明確一下,我們要實現的究竟是什麼?
Promise 實際上是一個規範,具體可以參考:Promise/A+
而我們要做的,僅僅是實現規範而已,接下來,讓我們一步一步,從易到難寫出一個簡單的 Promise 實現。
STEP 1 搭建基礎框架
Promise 是一個什麼?
按照我們的使用慣例,這類需要使用 new 關鍵字來生成的,通常是一個類。
class MyPromise {
constructor() {}
}
這樣,我們有了一個最簡單的 MyPromise 類。
其次,我們要想一想,在構造函數中,我們要怎麼去定義這個類,它的入參是什麼,又有哪些必備的屬性?
以下是我們常用的寫法:
new Promise((resolve, reject) => { ... })
由此可見,入參是一個 function,而且,這個 function 裏還包含了兩個入參,分別爲 resolve 方法和 reject 方法。
此外,根據規範,Promise 的實例應該具備 state(狀態),value(值)和 reason(異常原因)。
另外,它的實例還有 then()方法、catch()方法。(try 和 finally 方法的提案太新,可以暫時不實現)
此外,它還包含四個靜態方法:resolve()、reject()、all()、race()。
知道了這些,我們就可以搭建出一個基本的框架了。
class MyPromise {
constructor(fn) {
this.state = undefined // 狀態
this.value = undefined // 值
this.reason = undefined // 異常原因
this.then = (callback) => {} // then 方法
this.catch = (callback) => {} // catch方法
}
// 以下皆爲靜態方法
static resolve(value){ }
static reject(value){ }
static all(promiseArr) { }
static race(promiseArr) {}
}
如上,就是 MyPromise 的基本骨架了,下一節,就讓我們來實現其中最核心的構造函數,以及.then 方法吧。
STEP 2 核心:構造函數與 then 函數
首先,讓我們給三個狀態定義一下常量,並且初始化一下
const PENDING = 'pending'
const RESOLVED = 'resolved'
const REJECTED = 'rejected'
class MyPromise {
constructor(fn) {
this.state = PENDING
...
}
}
接下來,我們需要處理一下入參 fn 的問題。
這個方法,它還包含兩個入參,並且其中的內容,是在實例化時立刻執行的,並且是同步的。請看下面的例子:
new Promise((resolve, reject) => {console.log(1)})
console.log(2)
// 1
// 2
因此,我們代碼如下:
class MyPromise {
constructor(fn) {
this.state = PENDING
this.value = undefined
this.reason = undefined
this._resolve = res => { }
this._reject = err => { }
fn(this._resolve, this._reject)
...
}
...
}
此時,我們需要好好考慮一下,當入參的 fn 中,調用 resolve 方法時,都發生了什麼?
首先,實例的狀態會變,從'pending'變爲'resolved',實例的 value 屬性會被賦值,然後,實例的.then(onFulfilled, onRejected) 中的 onFulfilled 方法會被調用,且 res 這個入參就是 resolve(x) 中返回的值 x。
reject 時同理,不過調用的是 onRejected;
但這裏有個細節需要注意。
那就是 then 方法的回調,是異步的,而狀態的改變卻是同步,這裏我們可以寫個例子看看。
const a = new Promise((resolve, reject) => {resolve(1)}).then(res => console.log(1, a))
console.log(2, a)
// 2 {value: 1, state: 'resolved'}
// 1 {value: 1, state: 'resolved'}
可以看出來,Promise 先執行了同步的內容,才執行的回調中的內容,而且在同步的 console.log(2, a)中,其 state 已經變成了'resolved'。
而且,.then()方法實際返回的夜市一個 Promise 實例,這樣纔可以保證鏈式調用。
因此,此處我們可以簡單地實現爲:
class MyPromise {
constructor(fn) {
...
this.then = (onFulfilled, onRejected) => {
this.onRejected = onRejected
return new MyPromise((resolve, reject) => {
this.thenMethod = res => {
// 此處先不考慮.then中返回的是Promise實例的情況
// 將then中onFulfilled的執行內容先封裝個方法存起來,後期再執行
const onFulfilledRes = onFulfilled(res)
resolve(onFulfilledRes)
}
})
}
this._resolve = res => {
if (this.state !== PENDING) {
return
}
this.value = res
this.state = RESOLVED
setTimeout(() => {
// 異步地執行.then中被保存起來的,onFulfilledRes的內容
this.thenMethod && this.thenMethod(res)
}, 0)
}
}
}
這樣,一個最簡單的 Promise 的核心就出來了。
再加上異常處理和捕獲,主體部分就能完成撰寫:
const PENDING = 'pending'
const RESOLVED = 'resolved'
const REJECTED = 'rejected'
class MyPromise {
constructor(fn) {
this.state = PENDING
this.value = undefined
this.reason = undefined
this._resolve = res => {
if (this.state !== PENDING) {
return
}
setTimeout(() => {
this.value = res
this.state = RESOLVED
this.thenMethod && this.thenMethod(res)
}, 0)
}
this.then = (onFulfilled, onRejected) => {
this.onRejected = onRejected
return new MyPromise((resolve, reject) => {
this.thenMethod = res => {
if (onFulfilled instanceof Function) {
try {
const onFulfilledRes = onFulfilled(res)
if (onFulfilledRes instanceof MyPromise) {
onFulfilledRes.then(res => {
resolve(res)
})
} else {
resolve(onFulfilledRes)
}
} catch (err) {
reject(err)
}
}
}
})
}
this._reject = err => {
this.state = REJECTED
this.reason = err
if (this.onRejected) {
this.onRejected(err)
return
}
this.onException && this.onException(err)
}
this.catch = onException => {
this.onException = onException
}
fn(this._resolve, this._reject)
}
}
STEP 3 收汁,幾個簡單的靜態方法
衆所周知,es6 的 class 標準,是允許定義靜態方法的,如下:
class A {
static sayHello(){console.log('hello')}
}
A.sayHello()
// hello
靜態方法不屬於某個實例,而是屬於這個類本身,Promise.resolve()、Promise.reject()、Promise.all()、Promise.race()
在已經鋪好了核心代碼的情況下,靜態方法顯得如此輕鬆。
class MyPromise {
...
/**
* @param {any} value
* @returns {MyPromise}
*/
static resolve(value) {
if (value instanceof MyPromise) {
return MyPromise
}
return new MyPromise((resolve, reject) => {
resolve(value)
})
}
/**
* @param {any} value
* @returns {MyPromise}
*/
static reject(value) {
if (value instanceof MyPromise) {
return MyPromise
}
return new MyPromise((resolve, reject) => {
reject(value)
})
}
/**
* @param {Iterator} promiseArr
* @returns {MyPromise}
* @description
* 1. 首先解釋下入參,大多數情況下Promise.all的入參是一個數組,數組的每個元素都是Promise實例,
* 但實際上入參允許是具備Iterator接口的對象,但返回的每個成員都必須是Promise實例
* 2. 其原則爲, Iterator接口的每個成員, 狀態都爲fulfilled時, 返回的Promise實例變爲fulfilled狀態,且
* resolve方法入參爲一個數組,數組的元素爲每個Promise實例的返回值。
* 3. 方法返回的Promise實例的.catch方法, 會拋出Iterator接口中, 第一個變爲rejected狀態的異常值。
*/
static all(promiseArr) {
// 先將遍歷體轉換爲數組,然後將非promise元素轉換爲Promis實例
const MyPromiseArr = Array.from(promiseArr).map(item => {
if (item instanceof MyPromise) {
return item
}
return MyPromise.resolve(item)
})
return new MyPromise((resolve, reject) => {
let count = 0
let totle = promiseArr.length
const resArr = new Array(totle)
MyPromiseArr.forEach((item, index) => {
item
.then(res => {
resArr[index] = res
count++
if (count == totle) {
resolve(resArr)
}
})
.catch(err => {
reject(err)
})
})
})
}
/**
* @param {Iterator} promiseArr
* @returns {MyPromise}
* @description
* 1. 首先解釋下入參,大多數情況下Promise.all的入參是一個數組,數組的每個元素都是Promise實例,
* 但實際上入參允許是具備Iterator接口的對象,但返回的每個成員都必須是Promise實例;(如果不是promise,則轉換成promise)
* 2. 其原則爲, Iterator接口的每個成員, 第一個狀態都爲fulfilled時, 返回的Promise實例變爲fulfilled狀態,且
* resolve方法入參爲一個數組,數組的元素爲第一個Promise實例的返回值。
* 3. 方法返回的Promise實例的.catch方法, 會拋出Iterator接口中, 第一個變爲rejected狀態的異常值。
*/
static race(promiseArr) {
const MyPromiseArr = Array.from(promiseArr).map(item => {
if (item instanceof MyPromise) {
return item
}
return MyPromise.resolve(item)
})
return new MyPromise((resolve, reject) => {
MyPromiseArr.forEach(item => {
item
.then(res => {
resolve(res)
})
.catch(err => {
reject(err)
})
})
})
}
}
總結
好了,現在,一個完整的 Promise 就被我們手寫出來了。
完整代碼已經上傳了 github,感興趣的朋友可以 down 下來看看,並且歡迎給我一顆寶貴的小星星。
代碼地址
謝謝!