如何實現Promise polyfill(es5版)
前言前幾天看了很多篇實現promise的文章,發現很大一部分同學都是用的es6進行的實現。但是心裏一直有一個疑問,如果都用es6來進行編寫了,那麼這個運行環境肯定已經支持了原生的promise了呀。所以在這裏我準備僅使用es5來對Promise實現一個polyfill,實現了promise包括then,catch,reject,resolve,all,race,finally,done等方法。life is short,show me the code,下面對於實現我儘量都寫在代碼註釋裏面,沒有按照單獨每個屬性分板塊進行講解,儘量不打亂大家的閱讀節奏。
參考文章和其他鏈接
文章參考了https://segmentfault.com/a/1190000021320472,該文對每一個方法都進行了解釋,還有generator和async,await的介紹。
我另外還寫了一篇關於前端路由的文章,歡迎大家指正:https://blog.csdn.net/jind325/article/details/105325221
代碼實現
// 判斷變量否爲function
var isFunction = variable => typeof variable === 'function'
// 定義Promise的三種狀態常量
var PENDING = 'PENDING'
var FULFILLED = 'FULFILLED'
var REJECTED = 'REJECTED'
var JPromise = function(handle) {
if (!isFunction(handle)) {
throw new Error('JPromise must accept a function as a parameter')
}
// 添加狀態
this._status = PENDING
// 添加狀態
this._value = undefined
// 添加成功回調函數隊列, 這裏是爲了then方法做準備,每調用一次then方法都會添加一個回調
this._fulfilledQueues = []
// 添加失敗回調函數隊列
this._rejectedQueues = []
// 執行handle
try {
handle(this._resolve.bind(this), this._reject.bind(this))
} catch (err) {
this._reject(err)
}
}
//定義resolve時執行函數
JPromise.prototype._resolve = function(val) {
var self = this
//考慮到promise使用resolve時存在兩種情況,一種是直接resolve一個值,一種是resolve一個promise,
//在這裏都需要考慮進來;在這裏resolve代碼應該設置爲異步執行保證同一個回調中的其他同步代碼不被跳過
//如果狀態不爲pending,則不執行接下來的代碼,否則就把改promise的狀態置爲fullfilled
if (this._status !== PENDING) return
var run = function () {
self._status = FULFILLED
//當fullfilled時,我們需要把_fulfilledQueues,成功回調隊列中的所有的函數給清空掉
var runFulfilled = function(value) {
var cd = null;
while (cb = self._fulfilledQueues.shift()) {
cb(value)
}
}
//當rejected時,我們需要把_rejectedQueues,失敗回調隊列中的所有的函數給清空掉
var runRejected = function(error) {
var cb = null;
while (cb = self._rejectedQueues.shift()) {
cb(error)
}
}
//在這裏來進行resolve值val的處理,分爲val爲普通值和promise實例兩種情況
if (val instanceof JPromise) {
val.then(function(value){
self._value = value
runFulfilled(value)
}, function(err){
self._value = err
runRejected(err)
})
} else {
self._value = val
runFulfilled(val)
}
}
//爲了保證resolve後面的同步代碼優先執行, 這裏設置爲異步
setTimeout(run, 0)
};
//ok我們再來定義reject函數,相較於resolve,我們只需要處理失敗時的回調隊列即可
JPromise.prototype._reject = function(err) {
var self = this
if (this._status !== PENDING) return
var run = function() {
self._status = REJECTED
self._value = err
var cb
while (cb = self._rejectedQueues.shift()) {
cb(err)
}
}
//理由同上
setTimeout(run, 0)
}
//then方法
JPromise.prototype.then = function(onFulfilled, onRejected) {
var _status = this._status
var _value = this._value
var self = this
//衆所周知,then方法應該返回一個promise,否則無法實現then的鏈式調用
return new JPromise(function (onFulfilledNext, onRejectedNext) {
//封裝成功時的執行函數
var fulfilled = function(value) {
try {
if (!isFunction(onFulfilled)) {
onFulfilledNext(value)
} else {
var res = onFulfilled(value);
if (res instanceof JPromise) {
// 如果當前回調函數返回JPromise對象,必須等待其狀態改變後在執行下一個回調
res.then(onFulfilledNext, onRejectedNext)
} else {
//否則會將返回結果直接作爲參數,傳入下一個then的回調函數,並立即執行下一個then的回調函數
onFulfilledNext(res)
}
}
} catch (err) {
debugger;
// 如果函數執行出錯,新的Promise對象的狀態爲失敗
onRejectedNext(err)
}
}
//封裝失敗時的執行函數
var rejected = function(error) {
try {
if (!isFunction(onRejected)) {
onRejectedNext(error)
} else {
var res = onRejected(error);
if (res instanceof JPromise) {
// 如果當前回調函數返回JPromise對象,必須等待其狀態改變後在執行下一個回調
res.then(onFulfilledNext, onRejectedNext)
} else {
//否則會將返回結果直接作爲參數,傳入下一個then的回調函數,並立即執行下一個then的回調函數
onFulfilledNext(res)
}
}
} catch (err) {
// 如果函數執行出錯,新的Promise對象的狀態爲失敗
onRejectedNext(err)
}
}
switch (_status) {
// 當狀態爲pending時,將then方法回調函數加入執行隊列等待執行
case PENDING:
self._fulfilledQueues.push(fulfilled)
self._rejectedQueues.push(rejected)
break
// 當狀態已經改變時,立即執行對應的回調函數
case FULFILLED:
fulfilled(_value)
break
case REJECTED:
rejected(_value)
break
}
})
}
//catch方法
JPromise.prototype.catch = function(onRejected) {
return this.then(undefined, onRejected)
}
//resolve靜態方法,即直接調用JPromise.resolve使用, 不能被實例繼承
JPromise.resolve = function(value) {
// 如果參數是JPromise實例,直接返回這個實例
if (value instanceof JPromise) return value
return new JPromise(function(resolve) {resolve(value)})
}
// 添加靜態reject方法
JPromise.reject = function(value) {
if(value instanceof JPromise) return value
return new JPromise(function(resolve ,reject) {reject(value)})
}
// 添加靜態all方法, 需要返回一個JPromise,當list中所有JPromise狀態都爲resolve時,外層JPromise被resolve;有一個內部JPromise被reject則被rejected
JPromise.all = function(list) {
var self = this
return new JPromise(function(resolve, reject) {
//返回值的集合
var values = []
var count = 0
list.forEach(function(p, i) {
self.resolve(p).then(function(res) {
values[i] = res
count++
// 所有狀態都變成fulfilled時返回的JPromise狀態就變成fulfilled
if (count === list.length) resolve(values)
}, function(err) {
// 有一個被rejected時返回的JPromise狀態就變成rejected
reject(err)
}
)})
})
}
// 添加靜態race方法, 和all類似,只是list有一個promise狀態爲fullfiled時, 就改變整個外層promise狀態
JPromise.race = function (list) {
var self = this
return new JPromise(function(resolve, reject) {
list.forEach(function(p, i) {
self.resolve(p).then(function(res) {
resolve(res)
}, function(err) {
// 有一個被rejected時返回的JPromise狀態就變成rejected
reject(err)
}
)})
})
}
//finnally和then功能很類似,只是裏面的回調永遠都是在隊列的最後執行
JPromise.prototype.finally = function(cb) {
var self = this
return self.then(
function(value) {JPromise.resolve(cb()).then(function() {return value})},
function(reason) {JPromise.resolve(cb()).then(function() {throw reason })}
);
}
JPromise.prototype.done = function(onFulfilled, onRejected) {
var self = this
self.then(onFulfilled, onRejected)
.catch(function (reason) {
console.log('reason',reason)
// 拋出一個全局錯誤
throw(`錯誤爲${reason}`)
})
}
測試用例
下面提供了幾個測試該promise實現的幾個測試用例,只需要修改order值即可,大家也可以幫忙補充測試一下,有問題歡迎評論區指出!
let order=1
switch (order) {
// 測試用例1:測試 resolve,then 和 catch
case 1:
new JPromise((resolve,reject)=>{
resolve('這是第一個 resolve 值')
}).then((data)=>{
console.log(data) //會打印'這是第一個 resolve 值'
return '這是第二個 resolve 值'
}).then(data=>{
console.log(data)
throw('這是reject 值')
})
.catch((err)=>{
console.log(err)
})
break
// 測試用例 2:測試靜態方法resolve 和 reject
case 2:
JPromise.resolve('這是靜態方法的第一個 resolve 值').then((data)=>{
console.log(data) // 會打印'這是靜態方法的第一個 resolve 值'
})
JPromise.reject('這是靜態方法的第一個 reject 值').then((data)=>{
}).catch(err=>{
console.log(err) //會打印'這是靜態方法的第一個 reject 值'
})
break
// 測試用例 3:測試靜態方法all,race
case 3:
let pOne = new JPromise((resolve, reject) => {
resolve(1);
});
let pTwo = new JPromise((resolve, reject) => {
resolve(2);
});
let pThree = new JPromise((resolve, reject) => {
resolve(3);
});
JPromise.all([pOne, pTwo, pThree]).then(data => {
console.log('all: ',data); // [1, 2, 3] 正常執行完畢會執行這個,結果順序和promise實例數組順序是一致的
}, err => {
console.log(err); // 任意一個報錯信息
});
JPromise.race([pOne, pTwo, pThree]).then(data => {
console.log('race: ', data); // 1 正常執行完畢會執行這個,結果順序和promise實例數組順序是一致的
}, err => {
console.log(err); // 任意一個報錯信息
});
break
// 測試用例 4 finally 和 done
case 4:
JPromise.resolve('這是靜態方法的第一個 resolve 值').finally(()=>{
console.log('resolve 的finally')
})
JPromise.reject('這是靜態方法的第一個 reject 值').catch(err=>{
console.log(err)
}).finally(()=>{
console.log('reject 的finally')
})
JPromise.resolve('這是靜態方法的第一個 resolve 值').then(()=>{
return '這是靜態方法的第二個 resolve 值'
}).then(()=>{
throw('這是靜態方法的第一個 reject 值')
return '這是靜態方法的第二個 resolve 值'
}).done()
break
default:
break
}
以上polyfill代碼和測試用例都可以直接在瀏覽器console運行輸出,歡迎大家ctrl cv跑一跑
總結
以上就是Promise在es5規範下的一個polyfill,如果各位覺得寫得還不錯,歡迎點贊和收藏,如有紕漏,歡迎評論指正,萬分感謝