爲更好的理解, 推薦閱讀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/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
解決過程,這是爲了不同的Promise
都可以兼容使用,比如JQuery
的Promise
能兼容ES6
的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+
規範的實現