Promise 作為由社區(qū)提出和實現(xiàn)的異步編程解決方案塘砸,ES6 將其寫進了語言標準,統(tǒng)一了用法确封,原生提供了 Promise 對象需五。本文將剖析 Promise 內部標準鹉动,根據 Promises/A+ 規(guī)范從零實現(xiàn)一個 Promise。
Promise 構造函數(shù)
在 Promise 構造函數(shù)中宏邮,主要操作是初始化狀態(tài)和數(shù)據以及執(zhí)行函數(shù)參數(shù)泽示。
首先需要將狀態(tài)初始化為 pending,然后定義 Promise 的值以及回調函數(shù)集蜜氨。
const PENDING = 'pending'
const RESOLVED = 'resolved'
const REJECTED = 'rejected'
function MyPromise(executor) {
let self = this
self.status = PENDING // Promise 狀態(tài)械筛,初始狀態(tài)為 pending
self.data = undefined // Promise 的值
self.onResolvedCallback = [] // Promise resolve 時的回調函數(shù)集
self.onRejectedCallback = [] // Promise reject 時的回調函數(shù)集
// 待完善,resolve 和 reject 函數(shù)
// 待完善记劝,執(zhí)行 executor 函數(shù)
}
在構造函數(shù)中变姨,還需要執(zhí)行由外部傳進來的 executor 函數(shù)族扰,executor 函數(shù)中有兩個函數(shù)參數(shù)厌丑,分別為 resolve 和 reject 函數(shù)。
function MyPromise(executor) {
let self = this
self.status = PENDING // Promise 狀態(tài)渔呵,初始狀態(tài)為 pending
self.data = undefined // Promise 的值
self.onResolvedCallback = [] // Promise resolve 時的回調函數(shù)集
self.onRejectedCallback = [] // Promise reject 時的回調函數(shù)集
function resolve(value) {
// 當狀態(tài)為 pending 時怒竿,改變狀態(tài)為 resolved,存儲 Promise 值以及執(zhí)行回調函數(shù)集
if (self.status === PENDING) {
self.status = RESOLVED
self.data = value
self.onResolvedCallback.map(cb => cb(value))
}
}
function reject(reason) {
// 當狀態(tài)為 pending 時扩氢,改變狀態(tài)為 rejected耕驰,存儲 Promise 值以及執(zhí)行回調函數(shù)集
if (self.status === PENDING) {
self.status = REJECTED
self.data = reason
self.onRejectedCallback.map(cb => cb(reason))
}
}
try {
executor(resolve, reject)
} catch (e) {
// executor 函數(shù)執(zhí)行中拋出錯誤時該 Promise 應該被 reject
reject(e)
}
}
executor 函數(shù)需要使用 try catch 包裹執(zhí)行的原因則是在 executor 函數(shù)執(zhí)行中可能會拋出錯誤,當拋出錯誤時則該 Promise 應該被 reject录豺,如下情況:
// 該 Promise 應該被 reject
new Promise(function(resolve, reject) {
throw 2
})
then 方法
then 方法主要是根據 Promise 當前狀態(tài)處理相應的邏輯朦肘,返回一個新的 Promise饭弓,新 Promise 的狀態(tài)由原先 Promise 的狀態(tài)和 then 方法函數(shù)參數(shù)中的返回值決定。
當 then 方法執(zhí)行時媒抠,該 Promise 的狀態(tài)是不確定的弟断,所以需要對 Promise 的狀態(tài)進行判斷然后執(zhí)行不同的操作,then 方法會返回一個新的 Promise趴生。
MyPromise.prototype.then = function (onResolved, onRejected) {
let self = this
let promise2
// 如果 then 的參數(shù)不是 function阀趴,則我們需要賦予默認函數(shù)實現(xiàn)值的透傳
onResolved = typeof onResolved === 'function' ? onResolved : value => value
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }
if (self.status === RESOLVED) {
return promise2 = new MyPromise((resolve, reject) => {
})
}
if (self.status === REJECTED) {
return promise2 = new MyPromise((resolve, reject) => {
})
}
if (self.status === PENDING) {
return promise2 = new MyPromise((resolve, reject) => {
})
}
}
then 方法會返回一個新的 Promise 后,新 Promise 的狀態(tài)由原先 Promise 的狀態(tài)和 then 方法函數(shù)參數(shù)中的返回值決定苍匆。
MyPromise.prototype.then = function (onResolved, onRejected) {
let self = this
let promise2
// 如果 then 的參數(shù)不是 function刘急,則我們需要賦予默認函數(shù)實現(xiàn)值的透傳
onResolved = typeof onResolved === 'function' ? onResolved : value => value
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }
if (self.status === RESOLVED) {
return promise2 = new MyPromise((resolve, reject) => {
try {
// 執(zhí)行 onResolved 函數(shù)并獲取返回值。若返回值是 Promise 對象浸踩,則取它的結果作為 promise2 的結果叔汁,否則以返回值作為 promise2 的結果
var x = onResolved(self.data)
if (x instanceof MyPromise) {
x.then(resolve, reject)
}
resolve(x)
} catch (e) {
// 拋出錯誤則以捕獲到的錯誤作為 promise2 的結果
reject(e)
}
})
}
if (self.status === REJECTED) {
return promise2 = new MyPromise((resolve, reject) => {
try {
// 執(zhí)行 onRejected 函數(shù)并獲取返回值。若返回值是 Promise 對象检碗,則取它的結果作為 promise2 的結果攻柠,否則以返回值作為 promise2 的結果
var x = onRejected(self.data)
if (x instanceof MyPromise) {
x.then(resolve, reject)
}
reject(x)
} catch (e) {
// 拋出錯誤則以捕獲到的錯誤作為 promise2 的結果
reject(e)
}
})
}
if (self.status === PENDING) {
return promise2 = new MyPromise((resolve, reject) => {
// 將回調函數(shù)存進回調函數(shù)集
self.onResolvedCallback.push((value) => {
try {
var x = onResolved(self.data)
if (x instanceof MyPromise) {
x.then(resolve, reject)
}
resolve(x)
} catch (e) {
reject(e)
}
})
self.onRejectedCallback.push((reason) => {
try {
var x = onRejected(self.data)
if (x instanceof MyPromise) {
x.then(resolve, reject)
}
reject(x)
} catch (e) {
reject(e)
}
})
})
}
}
在 then 方法中根據 Promise 的當前狀態(tài)分別執(zhí)行了不同的操作。當狀態(tài)為 resolved 時后裸,執(zhí)行 onResolved 函數(shù)(then 方法第一個函數(shù)參數(shù))并根據返回值確定 promise2 的狀態(tài)瑰钮;當狀態(tài)為 rejected 時,執(zhí)行 onRejected 函數(shù)(then 方法第二個函數(shù)參數(shù))并根據返回值確定 promise2 的狀態(tài)微驶;當狀態(tài)為 pending 時浪谴,則需要將 onResolved 和 onRejected 函數(shù)先存進回調函數(shù)集中,等到 Promise 狀態(tài)改變后再執(zhí)行因苹。
而在代碼注釋中說明苟耻,如果 then 的參數(shù)不是 function,則我們需要賦予默認函數(shù)實現(xiàn)值的透傳扶檐。
當傳進 then 方法中 onResolved 或 onRejected 函數(shù)參數(shù)為空時凶杖,則應該賦予它們一個默認函數(shù),該默認函數(shù) return 或 throw 原先的參數(shù)值款筑,這樣才能正確實現(xiàn) then 方法的鏈式調用智蝠,如下:
new MyPromise((resolve, reject) => { resolve(1) })
.then()
.then()
.then((value) => {
console.log(value)
})
至此,我們便完成了一個符合 Promises/A+ 規(guī)范的 Promise 基礎版奈梳,同原生 Promise 一樣杈湾,可以通過如下方式使用:
let myPromise = new MyPromise((resolve, reject) => {
if (/* 異步操作成功 */) {
resolve(value);
} else {
reject(error);
}
});
myPromise.then((value) => {
console.log(value)
}, (err) => {
console.log(err)
})
Promise 終極版
上述的代碼已經能夠實現(xiàn)一個基本 Promise 的功能,而在實際使用過程中攘须,我們還需要根據 Promises/A+ 規(guī)范繼續(xù)完善它漆撞。
需要完善的主要有以下兩點:
- 不同 Promise 之間的兼容;
- 異步調用操作;
在實際中浮驳,有多種不同的 Promise 實現(xiàn)悍汛,關于不同 Promise 間的交互, Promises/A+ 規(guī)范已經做了詳細的說明至会,其中詳細指定了如何通過 then 的實參的返回值來決定 promise2 的狀態(tài)员凝,我們只需要按照標準將內容轉成代碼即可。
function resolvePromise(promise2, x, resolve, reject) {
var then
var thenCalledOrThrow = false
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise!'))
}
if (x instanceof Promise) {
if (x.status === 'pending') {
x.then(function(value) {
resolvePromise(promise2, value, resolve, reject)
}, reject)
} else {
x.then(resolve, reject)
}
return
}
if ((x !== null) && ((typeof x === 'object') || (typeof x === 'function'))) {
try {
then = x.then
if (typeof then === 'function') {
then.call(x, function rs(y) {
if (thenCalledOrThrow) return
thenCalledOrThrow = true
return resolvePromise(promise2, y, resolve, reject)
}, function rj(r) {
if (thenCalledOrThrow) return
thenCalledOrThrow = true
return reject(r)
})
} else {
resolve(x)
}
} catch (e) {
if (thenCalledOrThrow) return
thenCalledOrThrow = true
return reject(e)
}
} else {
resolve(x)
}
}
所以奋献,在 then 方法中健霹,我們不再需要判斷返回值 x 的類型,然后再根據 x 的類型去決定 promise2 的狀態(tài)瓶蚂,只需要將其傳入 resolvePromise 函數(shù)即可糖埋。
// self.status === RESOLVED 部分更改,其余兩個狀態(tài)更改同理
var x = onResolved(self.data)
if (x instanceof MyPromise) {
x.then(resolve, reject)
}
resolve(x)
=>
var x = onResolved(self.data)
resolvePromise(promise2, x, resolve, reject)
最后窃这,在標準中瞳别,說明了某些地方需要使用異步調用,在我們的實現(xiàn)中杭攻,我們需要在 resolve祟敛、reject、onResolved兆解、onRejected 加上異步調用的代碼馆铁,這里我們使用 setTimeout(fn, 0) 來實現(xiàn)。
至此锅睛,我們實現(xiàn)了一個符合 Promises/A+ 規(guī)范的終極版 Promise埠巨,如下:
const PENDING = 'pending'
const RESOLVED = 'resolved'
const REJECTED = 'rejected'
function MyPromise(executor) {
let self = this
self.status = PENDING // Promise 狀態(tài),初始狀態(tài)為 pending
self.data = undefined // Promise 的值
self.onResolvedCallback = [] // Promise resolve 時的回調函數(shù)集
self.onRejectedCallback = [] // Promise reject 時的回調函數(shù)集
function resolve(value) {
setTimeout(() => { // 異步回調
// 當狀態(tài)為 pending 時现拒,改變狀態(tài)為 resolved辣垒,存儲 Promise 值以及執(zhí)行回調函數(shù)集
if (self.status === PENDING) {
self.status = RESOLVED
self.data = value
self.onResolvedCallback.map(cb => cb(value))
}
}, 0)
}
function reject(reason) {
setTimeout(() => { // 異步回調
// 當狀態(tài)為 pending 時,改變狀態(tài)為 rejected印蔬,存儲 Promise 值以及執(zhí)行回調函數(shù)集
if (self.status === PENDING) {
self.status = REJECTED
self.data = reason
self.onRejectedCallback.map(cb => cb(reason))
}
}, 0)
}
try {
executor(resolve, reject)
} catch (e) {
// executor 函數(shù)執(zhí)行中拋出錯誤時該 Promise 應該被 reject
reject(e)
}
}
MyPromise.prototype.then = function (onResolved, onRejected) {
let self = this
let promise2
// 如果 then 的參數(shù)不是 function勋桶,則我們需要賦予默認函數(shù)實現(xiàn)值的透傳
onResolved = typeof onResolved === 'function' ? onResolved : value => value
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }
if (self.status === RESOLVED) {
return promise2 = new MyPromise((resolve, reject) => {
setTimeout(() => { // 異步回調
try {
// 執(zhí)行 onResolved 函數(shù)并獲取返回值。若返回值是 Promise 對象例驹,則取它的結果作為 promise2 的結果,否則以返回值作為 promise2 的結果
var x = onResolved(self.data)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
// 拋出錯誤則以捕獲到的錯誤作為 promise2 的結果
reject(e)
}
}, 0)
})
}
if (self.status === REJECTED) {
return promise2 = new MyPromise((resolve, reject) => {
setTimeout(() => { // 異步回調
try {
// 執(zhí)行 onRejected 函數(shù)并獲取返回值眠饮。若返回值是 Promise 對象,則取它的結果作為 promise2 的結果,否則以返回值作為 promise2 的結果
var x = onRejected(self.data)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
// 拋出錯誤則以捕獲到的錯誤作為 promise2 的結果
reject(e)
}
}, 0)
})
}
if (self.status === PENDING) {
return promise2 = new MyPromise((resolve, reject) => {
// 將回調函數(shù)存進回調函數(shù)集
self.onResolvedCallback.push((value) => {
try {
var x = onResolved(self.data)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
self.onRejectedCallback.push((reason) => {
try {
var x = onRejected(self.data)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
})
}
}
function resolvePromise(promise2, x, resolve, reject) {
var then
var thenCalledOrThrow = false
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise!'))
}
if (x instanceof Promise) {
if (x.status === 'pending') {
x.then(function (value) {
resolvePromise(promise2, value, resolve, reject)
}, reject)
} else {
x.then(resolve, reject)
}
return
}
if ((x !== null) && ((typeof x === 'object') || (typeof x === 'function'))) {
try {
then = x.then
if (typeof then === 'function') {
then.call(x, function rs(y) {
if (thenCalledOrThrow) return
thenCalledOrThrow = true
return resolvePromise(promise2, y, resolve, reject)
}, function rj(r) {
if (thenCalledOrThrow) return
thenCalledOrThrow = true
return reject(r)
})
} else {
resolve(x)
}
} catch (e) {
if (thenCalledOrThrow) return
thenCalledOrThrow = true
return reject(e)
}
} else {
resolve(x)
}
}