JavaScript之手寫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/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+
規範的實現