作者:vivo互聯(lián)網(wǎng)技術(shù)-Morrain
Promise 是異步編程的一種解決方案鱼辙,它由社區(qū)最早提出和實(shí)現(xiàn),ES6 將其寫進(jìn)了語言標(biāo)準(zhǔn)黑滴,統(tǒng)一了用法屋厘,原生提供了Promise對象。更多關(guān)于 Promise 的介紹請參考阮一峰老師的ES6入門 之 Promise 對象咖气。
很多同學(xué)在學(xué)習(xí) Promise 時(shí)挨措,知其然卻不知其所以然,對其中的用法理解不了崩溪。本系列文章由淺入深逐步實(shí)現(xiàn) Promise浅役,并結(jié)合流程圖、實(shí)例以及動畫進(jìn)行演示伶唯,達(dá)到深刻理解 Promise 用法的目的觉既。
本系列文章有如下幾個(gè)章節(jié)組成:
圖解 Promise 實(shí)現(xiàn)原理(三)—— Promise 原型方法實(shí)現(xiàn)
圖解 Promise 實(shí)現(xiàn)原理(四)—— Promise 靜態(tài)方法實(shí)現(xiàn)
一、【前言】
上一節(jié)中乳幸,實(shí)現(xiàn)了 Promise 的鏈?zhǔn)秸{(diào)用瞪讼。鏈?zhǔn)秸{(diào)用是 Promise 的難點(diǎn),更是重點(diǎn)粹断。截至目前符欠,Promise 的實(shí)現(xiàn)如下:
class Promise {
callbacks = [];
state = 'pending';//增加狀態(tài)
value = null;//保存結(jié)果
constructor(fn) {
fn(this._resolve.bind(this));
}
then(onFulfilled) {
return new Promise(resolve => {
this._handle({
onFulfilled: onFulfilled || null,
resolve: resolve
});
});
}
_handle(callback) {
if (this.state === 'pending') {
this.callbacks.push(callback);
return;
}
//如果then中沒有傳遞任何東西
if (!callback.onFulfilled) {
callback.resolve(this.value);
return;
}
var ret = callback.onFulfilled(this.value);
callback.resolve(ret);
}
_resolve(value) {
if (value && (typeof value === 'object' || typeof value === 'function')) {
var then = value.then;
if (typeof then === 'function') {
then.call(value, this._resolve.bind(this));
return;
}
}
this.state = 'fulfilled';//改變狀態(tài)
this.value = value;//保存結(jié)果
this.callbacks.forEach(callback => this._handle(callback));
}
}
本節(jié)主要介紹 Promise 原型方法的實(shí)現(xiàn),包括 catch瓶埋、finally 以及 rejected 狀態(tài)等的實(shí)現(xiàn)希柿。
二诊沪、【錯(cuò)誤處理】
之前為了講解原理,只是實(shí)現(xiàn)了 onFulfilled 曾撤,對于 Promise 來說端姚,除了成功還有失敗,在失敗時(shí)挤悉,要標(biāo)記 Promise 的狀態(tài)為 rejected渐裸, 并執(zhí)行注冊的 onRejected。如下Demo所示:
/**
* 模擬異常異步請求
* @param {*} url
* @param {*} s
* @param {*} callback
*/
const mockAjax = (url, s, callback) => {
setTimeout(() => {
callback(url + '異步請求耗時(shí)' + s + '秒', '出錯(cuò)了!');
}, 1000 * s)
}
//demo reject
new Promise((resolve, reject) => {
mockAjax('getUserId', 1, function (result, error) {
if (error) {
reject(error)
} else {
resolve(result);
}
})
}).then(result => {
console.log(result);
}, error => {
console.log(error);
});
有了之前處理 fulfilled 狀態(tài)的經(jīng)驗(yàn)尖啡,支持錯(cuò)誤處理變得很容易橄仆,只需要在注冊回調(diào)剩膘、處理狀態(tài)變更上都要加入新的 reject 邏輯衅斩。
//完整的實(shí)現(xiàn)+reject
class Promise {
callbacks = [];
state = 'pending';//增加狀態(tài)
value = null;//保存結(jié)果
constructor(fn) {
fn(this._resolve.bind(this), this._reject.bind(this));
}
then(onFulfilled, onRejected) {
return new Promise((resolve, reject) => {
this._handle({
onFulfilled: onFulfilled || null,
onRejected: onRejected || null,
resolve: resolve,
reject: reject
});
});
}
_handle(callback) {
if (this.state === 'pending') {
this.callbacks.push(callback);
return;
}
let cb = this.state === 'fulfilled' ? callback.onFulfilled : callback.onRejected;
if (!cb) {//如果then中沒有傳遞任何東西
cb = this.state === 'fulfilled' ? callback.resolve : callback.reject;
cb(this.value);
return;
}
let ret = cb(this.value);
cb = this.state === 'fulfilled' ? callback.resolve : callback.reject;
cb(ret);
}
_resolve(value) {
if (value && (typeof value === 'object' || typeof value === 'function')) {
var then = value.then;
if (typeof then === 'function') {
then.call(value, this._resolve.bind(this), this._reject.bind(this));
return;
}
}
this.state = 'fulfilled';//改變狀態(tài)
this.value = value;//保存結(jié)果
this.callbacks.forEach(callback => this._handle(callback));
}
_reject(error) {
this.state = 'rejected';
this.value = error;
this.callbacks.forEach(callback => this._handle(callback));
}
}
運(yùn)行結(jié)果如下:
[Promse-1]:constructor
[Promse-1]:then
[Promse-2]:constructor
[Promse-1]:_handle state= pending
[Promse-1]:_handle callbacks= [ { onFulfilled: [Function],
onRejected: [Function],
resolve: [Function],
reject: [Function] } ]
=> Promise { callbacks: [], name: 'Promse-2', state: 'pending', value: null }
[Promse-1]:_reject
[Promse-1]:_reject value= 出錯(cuò)了!
[Promse-1]:_handle state= rejected
出錯(cuò)了!
[Promse-2]:_reject
[Promse-2]:_reject value= undefined
三、【異常處理】
剛剛介紹了錯(cuò)誤處理怠褐,是指在 Promise 的構(gòu)造函數(shù)中發(fā)現(xiàn)的錯(cuò)誤畏梆,并通過 reject 通知的。如果在執(zhí)行 onFulfilled 或者 onRejected 時(shí)奈懒,出現(xiàn)了異常奠涌,該如何處理呢?對于這類異常磷杏,處理也很簡單溜畅,可以使用 try-catch 捕獲錯(cuò)誤,然后將相應(yīng)的 Promise 狀態(tài)設(shè)置為 rejected 狀態(tài)极祸。改造_handle方法如下:
_handle(callback) {
if (this.state === 'pending') {
this.callbacks.push(callback);
return;
}
let cb = this.state === 'fulfilled' ? callback.onFulfilled : callback.onRejected;
if (!cb) {//如果then中沒有傳遞任何東西
cb = this.state === 'fulfilled' ? callback.resolve : callback.reject;
cb(this.value);
return;
}
let ret;
try {
ret = cb(this.value);
cb = this.state === 'fulfilled' ? callback.resolve : callback.reject;
} catch (error) {
ret = error;
cb = callback.reject
} finally {
cb(ret);
}
}
不管是錯(cuò)誤也好慈格,異常也罷,最終都是通過 reject 實(shí)現(xiàn)的遥金,可見最終對于錯(cuò)誤以及異常的處理浴捆,都可以通過 then 中的 onRejected 來處理。所以單獨(dú)增加一個(gè) catch 方法稿械,它是 .then(null, onRejected) 的別名选泻。如下:
then(onFulfilled, onRejected) {
return new Promise((resolve, reject) => {
this._handle({
onFulfilled: onFulfilled || null,
onRejected: onRejected || null,
resolve: resolve,
reject: reject
});
});
}
catch(onError){
return this.then(null, onError);
}
四、【Finally方法】
在實(shí)際應(yīng)用的時(shí)候美莫,我們很容易會碰到這樣的場景页眯,不管 Promise 最后的狀態(tài)如何,都要執(zhí)行某些操作(onDone)厢呵。例如服務(wù)器使用 Promise 處理請求窝撵,然后使用finally方法關(guān)掉服務(wù)器:
server.listen(port)
.then(function () {
// do something
})
.catch(error=>{
// handle error
})
.finally(server.stop);
本質(zhì)上,因?yàn)樗?then 的一種變形述吸。上面的 demo 的效果等價(jià)于如下的代碼:
server.listen(port)
.then(function () {
// do something
})
.catch(error=>{
// handle error
})
.then(server.stop, server.stop);
通過上面的分析忿族,finally看上去可以這么實(shí)現(xiàn):
finally(onDone){
return this.then(onDone, onDone);
}
但是由于 finally 方法的 onDone 不關(guān)心 Promise 的狀態(tài)到底是 fulfilled 還是 rejected 锣笨,所以onDone 里的操作,應(yīng)該是與狀態(tài)無關(guān)的道批,并且不應(yīng)該有任何參數(shù)错英。
如果使用 then 來實(shí)現(xiàn)就不符合 Promise 規(guī)范中關(guān)于《Why not .then(f, f)?》的說明 。一來 onDone 有參數(shù)隆豹,二來當(dāng) onDone 返回一個(gè)Promise時(shí)椭岩,會改變 finally 返回的Promise的值 狀態(tài) 。
根據(jù)規(guī)范璃赡,finally實(shí)現(xiàn)如下:
catch(onError) {
return this.then(null, onError);
}
finally(onDone) {
if (typeof onDone !== 'function') return this.then();
let Promise = this.constructor;
return this.then(
value => Promise.resolve(onDone()).then(() => value),
reason => Promise.resolve(onDone()).then(() => { throw reason })
);
}
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success');
}, 1000)
}).finally(() => {
console.log('onDone')
})
對于上面的示例判哥,執(zhí)行結(jié)果如下:
[Promse-1]:constructor
[Promse-1]:finally
[Promse-1]:then
[Promse-2]:constructor
[Promse-1]:_handle state= pending
[Promse-1]:_handle callbacks= [ { onFulfilled: [Function],
onRejected: [Function],
resolve: [Function],
reject: [Function] } ]
=> Promise { callbacks: [], name: 'Promse-2', state: 'pending', value: null }
[Promse-1]:_resolve
[Promse-1]:_resolve value= success
[Promse-1]:_handle state= fulfilled
onDone
Promise::resolve
[Promse-3]:constructor
[Promse-3]:_resolve
[Promse-3]:_resolve value= undefined
[Promse-3]:then
[Promse-4]:constructor
[Promse-3]:_handle state= fulfilled
[Promse-4]:_resolve
[Promse-4]:_resolve value= success
[Promse-2]:_resolve
[Promse-2]:_resolve value= Promise {
callbacks: [],
name: 'Promse-4',
state: 'fulfilled',
value: 'success' }
[Promse-4]:then
[Promse-5]:constructor
[Promse-4]:_handle state= fulfilled
[Promse-2]:_resolve
[Promse-2]:_resolve value= success
[Promse-5]:_resolve
[Promse-5]:_resolve value= undefined
同樣可以通過示意動畫來還原這個(gè)過程:
(Promise.finally 演示動畫)點(diǎn)擊打開>>
finally 的實(shí)現(xiàn)看起來簡單,實(shí)際理解還是比較困難碉考,對于上面的實(shí)例塌计,中間其實(shí)有5個(gè) Promise實(shí)例生成。如下圖所示:
原型方法就介紹這么多侯谁,下一節(jié)介紹兩個(gè)靜態(tài)方法锌仅。