為更好的理解鼎文, 推薦閱讀Promise/A+ 規(guī)范
實(shí)現(xiàn)一個(gè)簡易版 Promise
在完成符合 Promise/A+
規(guī)范的代碼之前冈止,我們可以先來實(shí)現(xiàn)一個(gè)簡易版 Promise
,因?yàn)樵诿嬖囍胁穸眨绻隳軐?shí)現(xiàn)出一個(gè)簡易版的 Promise
基本可以過關(guān)了川尖。
那么我們先來搭建構(gòu)建函數(shù)的大體框架
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 函數(shù)
// 待完善執(zhí)行 fn 函數(shù)
}
- 首先我們創(chuàng)建了三個(gè)常量用于表示狀態(tài),對(duì)于經(jīng)常使用的一些值都應(yīng)該通過常量來管理卓练,便于開發(fā)及后期維護(hù)
- 在函數(shù)體內(nèi)部首先創(chuàng)建了常量
that
,因?yàn)榇a可能會(huì)異步執(zhí)行购啄,用于獲取正確的this
對(duì)象 - 一開始
Promise
的狀態(tài)應(yīng)該是pending
-
value
變量用于保存resolve
或者reject
中傳入的值 -
resolvedCallbacks
和rejectedCallbacks
用于保存then
中的回調(diào)襟企,因?yàn)楫?dāng)執(zhí)行完Promise
時(shí)狀態(tài)可能還是等待中,這時(shí)候應(yīng)該把then
中的回調(diào)保存起來用于狀態(tài)改變時(shí)使用
接下來我們來完善 resolve 和 reject 函數(shù)狮含,添加在 MyPromise 函數(shù)體內(nèi)部
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))
}
}
這兩個(gè)函數(shù)代碼類似顽悼,就一起解析了
- 首先兩個(gè)函數(shù)都得判斷當(dāng)前狀態(tài)是否為等待中曼振,因?yàn)橐?guī)范規(guī)定只有等待態(tài)才可以改變狀態(tài)
- 將當(dāng)前狀態(tài)更改為對(duì)應(yīng)狀態(tài),并且將傳入的值賦值給
value
- 遍歷回調(diào)數(shù)組并執(zhí)行
完成以上兩個(gè)函數(shù)以后蔚龙,我們就該實(shí)現(xiàn)如何執(zhí)行 Promise
中傳入的函數(shù)了
try {
fn(resolve, reject)
} catch (e) {
reject(e)
}
- 實(shí)現(xiàn)很簡單冰评,執(zhí)行傳入的參數(shù)并且將之前兩個(gè)函數(shù)當(dāng)做參數(shù)傳進(jìn)去
- 要注意的是,可能執(zhí)行函數(shù)過程中會(huì)遇到錯(cuò)誤木羹,需要捕獲錯(cuò)誤并且執(zhí)行
reject
函數(shù)
最后我們來實(shí)現(xiàn)較為復(fù)雜的 then
函數(shù)
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)
}
}
首先判斷兩個(gè)參數(shù)是否為函數(shù)類型甲雅,因?yàn)檫@兩個(gè)參數(shù)是可選參數(shù)
當(dāng)參數(shù)不是函數(shù)類型時(shí),需要?jiǎng)?chuàng)建一個(gè)函數(shù)賦值給對(duì)應(yīng)的參數(shù)坑填,同時(shí)也實(shí)現(xiàn)了透傳抛人,比如如下代碼
// 該代碼目前在簡單版中會(huì)報(bào)錯(cuò)
// 只是作為一個(gè)透傳的例子
Promise.resolve(4).then().then((value) => console.log(value))
- 接下來就是一系列判斷狀態(tài)的邏輯,當(dāng)狀態(tài)不是等待態(tài)時(shí)脐瑰,就去執(zhí)行相對(duì)應(yīng)的函數(shù)妖枚。如果狀態(tài)是等待態(tài)的話,就往回調(diào)函數(shù)中
push
函數(shù)蚪黑,比如如下代碼就會(huì)進(jìn)入等待態(tài)的邏輯
new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve(1)
}, 0)
}).then(value => {
console.log(value)
})
以上就是簡單版 Promise
實(shí)現(xiàn)
實(shí)現(xiàn)一個(gè)符合 Promise/A+ 規(guī)范的 Promise
接下來大部分代碼都是根據(jù)規(guī)范去實(shí)現(xiàn)的盅惜。
我們先來改造一下 resolve
和 reject
函數(shù)
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)
}
- 對(duì)于
resolve
函數(shù)來說中剩,首先需要判斷傳入的值是否為Promise
類型 - 為了保證函數(shù)執(zhí)行順序忌穿,需要將兩個(gè)函數(shù)體代碼使用
setTimeout
包裹起來
接下來繼續(xù)改造 then
函數(shù)中的代碼,首先我們需要新增一個(gè)變量 promise2
结啼,因?yàn)槊總€(gè) then
函數(shù)都需要返回一個(gè)新的 Promise
對(duì)象掠剑,該變量用于保存新的返回對(duì)象,然后我們先來改造判斷等待態(tài)的邏輯
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)
}
})
}))
}
- 首先我們返回了一個(gè)新的
Promise
對(duì)象郊愧,并在Promise
中傳入了一個(gè)函數(shù) - 函數(shù)的基本邏輯還是和之前一樣朴译,往回調(diào)數(shù)組中
push
函數(shù) - 同樣,在執(zhí)行函數(shù)的過程中可能會(huì)遇到錯(cuò)誤属铁,所以使用了
try...catch
包裹 - 規(guī)范規(guī)定眠寿,執(zhí)行
onFulfilled
或者onRejected
函數(shù)時(shí)會(huì)返回一個(gè) x,并且執(zhí)行Promise
解決過程焦蘑,這是為了不同的Promise
都可以兼容使用盯拱,比如JQuery
的Promise
能兼容ES6
的Promise
接下來我們改造判斷執(zhí)行態(tài)的邏輯
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)
}
})
}))
}
- 其實(shí)大家可以發(fā)現(xiàn)這段代碼和判斷等待態(tài)的邏輯基本一致,無非是傳入的函數(shù)的函數(shù)體需要異步執(zhí)行例嘱,這也是規(guī)范規(guī)定的
- 對(duì)于判斷拒絕態(tài)的邏輯這里就不一一贅述了狡逢,留給大家自己完成這個(gè)作業(yè)
最后,當(dāng)然也是最難的一部分拼卵,也就是實(shí)現(xiàn)兼容多種 Promise
的 resolutionProcedure
函數(shù)
function resolutionProcedure(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('Error'))
}
}
首先規(guī)范規(guī)定了 x
不能與 promise2
相等奢浑,這樣會(huì)發(fā)生循環(huán)引用的問題,比如如下代碼
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)
}
這里的代碼是完全按照規(guī)范實(shí)現(xiàn)的腋腮。如果 x
為 Promise
的話雀彼,需要判斷以下幾個(gè)情況:
- 如果 x 處于等待態(tài)壤蚜,Promise 需保持為等待態(tài)直至 x 被執(zhí)行或拒絕
- 如果 x 處于其他狀態(tài),則用相同的值處理 Promise
當(dāng)然以上這些是規(guī)范需要我們判斷的情況徊哑,實(shí)際上我們不判斷狀態(tài)也是可行的仍律。
接下來我們繼續(xù)按照規(guī)范來實(shí)現(xiàn)剩余的代碼
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)
}
- 首先創(chuàng)建一個(gè)變量
called
用于判斷是否已經(jīng)調(diào)用過函數(shù) - 然后判斷 x 是否為對(duì)象或者函數(shù),如果都不是的話实柠,將 x 傳入
resolve
中 - 如果 x 是對(duì)象或者函數(shù)的話水泉,先把
x.then
賦值給then
,然后判斷then
的類型窒盐,如果不是函數(shù)類型的話草则,就將 x 傳入resolve
中 - 如果
then
是函數(shù)類型的話,就將 x 作為函數(shù)的作用域 this 調(diào)用之蟹漓,并且傳遞兩個(gè)回調(diào)函數(shù)作為參數(shù)炕横,第一個(gè)參數(shù)叫做resolvePromise
,第二個(gè)參數(shù)叫做rejectPromise
葡粒,兩個(gè)回調(diào)函數(shù)都需要判斷是否已經(jīng)執(zhí)行過函數(shù)份殿,然后進(jìn)行相應(yīng)的邏輯 - 以上代碼在執(zhí)行的過程中如果拋錯(cuò)了,將錯(cuò)誤傳入
reject
函數(shù)中
以上就是符合 Promise/A+
規(guī)范的實(shí)現(xiàn)