手寫 Promise
我們會通過手寫一個符合 Promise/A+ 規範的
Promise
來深入了解它,并且手寫
Promise
也是一道大廠常考題,在進入正題之前,推薦各位閱讀一下 Promise/A+ 規範,這樣才能更好地了解這個章節的代碼。
實作一個簡易版 Promise
在完成符合 Promise/A+ 規範的代碼之前,我們可以先來實作一個簡易版
Promise
,因為在面試中,如果你能實作出一個簡易版的
Promise
基本可以過關了。
那麼我們先來搭建建構函數的大體架構
const PENDING = 'pending'
const RESOLVED = 'resolved'
const REJECTED = 'rejected'
function MyPromise(fn) {
const that = this
that.state = PENDING
that.value = null
that.resolvedCallbacks = []
that.rejectedCallbacks = []
// 待完善 resolve 和 reject 函數
// 待完善執行 fn 函數
}
- 首先我們建立了三個常量用于表示狀态,對于經常使用的一些值都應該通過常量來管理,便于開發及後期維護
- 在函數體内部首先建立了常量
,因為代碼可能會異步執行,用于擷取正确的that
對象this
- 一開始
的狀态應該是Promise
pending
-
變量用于儲存value
或者resolve
中傳入的值reject
-
和resolvedCallbacks
用于儲存rejectedCallbacks
中的回調,因為當執行完then
時狀态可能還是等待中,這時候應該把Promise
中的回調儲存起來用于狀态改變時使用then
接下來我們來完善
resolve
和
reject
函數,添加在
MyPromise
函數體内部
function resolve(value) {
if (that.state === PENDING) {
that.state = RESOLVED
that.value = value
that.resolvedCallbacks.map(cb => cb(that.value))
}
}
function reject(value) {
if (that.state === PENDING) {
that.state = REJECTED
that.value = value
that.rejectedCallbacks.map(cb => cb(that.value))
}
}
這兩個函數代碼類似,就一起解析了
- 首先兩個函數都得判斷目前狀态是否為等待中,因為規範規定隻有等待态才可以改變狀态
- 将目前狀态更改為對應狀态,并且将傳入的值指派給
value
- 周遊回調數組并執行
完成以上兩個函數以後,我們就該實作如何執行
Promise
中傳入的函數了
try {
fn(resolve, reject)
} catch (e) {
reject(e)
}
- 實作很簡單,執行傳入的參數并且将之前兩個函數當做參數傳進去
- 要注意的是,可能執行函數過程中會遇到錯誤,需要捕獲錯誤并且執行
函數reject
最後我們來實作較為複雜的
then
函數
MyPromise.prototype.then = function(onFulfilled, onRejected) {
const that = this
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
onRejected =
typeof onRejected === 'function'
? onRejected
: r => {
throw r
}
if (that.state === PENDING) {
that.resolvedCallbacks.push(onFulfilled)
that.rejectedCallbacks.push(onRejected)
}
if (that.state === RESOLVED) {
onFulfilled(that.value)
}
if (that.state === REJECTED) {
onRejected(that.value)
}
}
- 首先判斷兩個參數是否為函數類型,因為這兩個參數是可選參數
- 當參數不是函數類型時,需要建立一個函數指派給對應的參數,同時也實作了透傳,比如如下代碼
// 該代碼目前在簡單版中會報錯 // 隻是作為一個透傳的例子 Promise.resolve(4).then().then((value) => console.log(value))
- 接下來就是一系列判斷狀态的邏輯,當狀态不是等待态時,就去執行相對應的函數。如果狀态是等待态的話,就往回調函數中
函數,比如如下代碼就會進入等待态的邏輯push
new MyPromise((resolve, reject) => { setTimeout(() => { resolve(1) }, 0) }).then(value => { console.log(value) })
以上就是簡單版
Promise
實作,下面的是實作完整版
Promise
的解析,相信看完完整版的你,一定會對于
Promise
的了解更上一層樓。
實作一個符合 Promise/A+ 規範的 Promise
這小節代碼需要大家配合規範閱讀,因為大部分代碼都是根據規範去實作的。
我們先來改造一下
resolve
和
reject
函數
function resolve(value) {
if (value instanceof MyPromise) {
return value.then(resolve, reject)
}
setTimeout(() => {
if (that.state === PENDING) {
that.state = RESOLVED
that.value = value
that.resolvedCallbacks.map(cb => cb(that.value))
}
}, 0)
}
function reject(value) {
setTimeout(() => {
if (that.state === PENDING) {
that.state = REJECTED
that.value = value
that.rejectedCallbacks.map(cb => cb(that.value))
}
}, 0)
}
- 對于
函數來說,首先需要判斷傳入的值是否為resolve
類型Promise
- 為了保證函數執行順序,需要将兩個函數體代碼使用
包裹起來setTimeout
接下來繼續改造
then
函數中的代碼,首先我們需要新增一個變量
promise2
,因為每個
then
函數都需要傳回一個新的
Promise
對象,該變量用于儲存新的傳回對象,然後我們先來改造判斷等待态的邏輯
if (that.state === PENDING) {
return (promise2 = new MyPromise((resolve, reject) => {
that.resolvedCallbacks.push(() => {
try {
const x = onFulfilled(that.value)
resolutionProcedure(promise2, x, resolve, reject)
} catch (r) {
reject(r)
}
})
that.rejectedCallbacks.push(() => {
try {
const x = onRejected(that.value)
resolutionProcedure(promise2, x, resolve, reject)
} catch (r) {
reject(r)
}
})
}))
}
- 首先我們傳回了一個新的
對象,并在Promise
中傳入了一個函數Promise
- 函數的基本邏輯還是和之前一樣,往回調數組中
函數push
- 同樣,在執行函數的過程中可能會遇到錯誤,是以使用了
包裹try...catch
- 規範規定,執行
或者onFulfilled
函數時會傳回一個onRejected
,并且執行x
解決過程,這是為了不同的Promise
都可以相容使用,比如 JQuery 的Promise
能相容 ES6 的Promise
Promise
接下來我們改造判斷執行态的邏輯
if (that.state === RESOLVED) {
return (promise2 = new MyPromise((resolve, reject) => {
setTimeout(() => {
try {
const x = onFulfilled(that.value)
resolutionProcedure(promise2, x, resolve, reject)
} catch (reason) {
reject(reason)
}
})
}))
}
- 其實大家可以發現這段代碼和判斷等待态的邏輯基本一緻,無非是傳入的函數的函數體需要異步執行,這也是規範規定的
- 對于判斷拒絕态的邏輯這裡就不一一贅述了,留給大家自己完成這個作業
最後,當然也是最難的一部分,也就是實作相容多種
Promise
的
resolutionProcedure
函數
function resolutionProcedure(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('Error'))
}
}
首先規範規定了
x
不能與
promise2
相等,這樣會發生循環引用的問題,比如如下代碼
let p = new MyPromise((resolve, reject) => {
resolve(1)
})
let p1 = p.then(value => {
return p1
})
然後需要判斷
x
的類型
if (x instanceof MyPromise) {
x.then(function(value) {
resolutionProcedure(promise2, value, resolve, reject)
}, reject)
}
這裡的代碼是完全按照規範實作的。如果
x
為
Promise
的話,需要判斷以下幾個情況:
- 如果
處于等待态,x
需保持為等待态直至Promise
被執行或拒絕x
- 如果
處于其他狀态,則用相同的值處理x
Promise
當然以上這些是規範需要我們判斷的情況,實際上我們不判斷狀态也是可行的。
接下來我們繼續按照規範來實作剩餘的代碼
let called = false
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
try {
let then = x.then
if (typeof then === 'function') {
then.call(
x,
y => {
if (called) return
called = true
resolutionProcedure(promise2, y, resolve, reject)
},
e => {
if (called) return
called = true
reject(e)
}
)
} else {
resolve(x)
}
} catch (e) {
if (called) return
called = true
reject(e)
}
} else {
resolve(x)
}
- 首先建立一個變量
用于判斷是否已經調用過函數called
- 然後判斷
是否為對象或者函數,如果都不是的話,将x
傳入x
中resolve
- 如果
是對象或者函數的話,先把x
指派給x.then
,然後判斷then
的類型,如果不是函數類型的話,就将then
傳入x
中resolve
- 如果
是函數類型的話,就将then
作為函數的作用域x
調用之,并且傳遞兩個回調函數作為參數,第一個參數叫做this
,第二個參數叫做resolvePromise
,兩個回調函數都需要判斷是否已經執行過函數,然後進行相應的邏輯rejectPromise
- 以上代碼在執行的過程中如果抛錯了,将錯誤傳入
函數中reject
以上就是符合 Promise/A+ 規範的實作了,歡迎在評論中與我互動。
小結
這一章節我們分别實作了簡單版和符合 Promise/A+ 規範的
Promise
,前者已經足夠應付大部分面試的手寫題目,畢竟寫出一個符合規範的
Promise
在面試中不大現實。後者能讓你更加深入地了解
Promise
的運作原理,做技術的深挖者。