then, catch, finally如何影響返回的Promise實(shí)例狀態(tài)

原創(chuàng)@前端司南雖然Promise是開發(fā)過程中使用非常頻繁的一個(gè)技術(shù)點(diǎn),但是它的一些細(xì)節(jié)可能很多人都沒有去關(guān)注過。我們都知道,.then, .catch, .finally都可以鏈?zhǔn)秸{(diào)用,其本質(zhì)上是因?yàn)榉祷亓艘粋€(gè)新的Promise實(shí)例,而這些Promise實(shí)例現(xiàn)在的狀態(tài)是什么或者將來會(huì)變成什么狀態(tài),很多人心里可能都沒個(gè)底。我自己也意識(shí)到了這一點(diǎn),于是我通過一些代碼試驗(yàn),發(fā)現(xiàn)了一些共性。如果您對(duì)這塊內(nèi)容還沒有把握,不妨看看。
閱讀本文前,您應(yīng)該對(duì)Promise有一些基本認(rèn)識(shí),比如:
Promise有pending,fulfilled,rejected三種狀態(tài),其決議函數(shù)resolve()能將Promise實(shí)例的狀態(tài)由pending轉(zhuǎn)為fulfilled,其決議函數(shù)reject()能將Promise實(shí)例的狀態(tài)由pending轉(zhuǎn)為rejected。Promise實(shí)例的狀態(tài)一旦轉(zhuǎn)變,不可再逆轉(zhuǎn)。
本文會(huì)從一些測(cè)驗(yàn)代碼入手,看看Promise的幾個(gè)原型方法在處理Promise狀態(tài)時(shí)的一些細(xì)節(jié),最后對(duì)它們進(jìn)行總結(jié)歸納,加深理解!
then的語法形式如下:
p.then(onFulfilled[,?onRejected]);
onFulfilled可以接受一個(gè)value參數(shù),作為Promise狀態(tài)決議為fulfilled的結(jié)果,onRejected可以接受一個(gè)reason參數(shù),作為Promise狀態(tài)決議為rejected的原因。
- 如果
onFulfilled或onRejected不返回值,那么.then返回的Promise實(shí)例的狀態(tài)會(huì)變成fulfilled,但是伴隨fulfilled的value會(huì)是undefined。
new?Promise((resolve,?reject)?=>?{
????resolve(1)
}).then(value?=>?{
????console.log('resolution?occurred,?and?the?value?is:?',?value)
},?reason?=>?{
????console.log('rejection?occurred,?and?the?reason?is:?',?reason)
}).then(value?=>?{
????console.log('resolution?of?the?returned?promise?occurred,?and?the?value?is:?',?value)
},?reason?=>?{
????console.log('rejection?of?the?returned?promise?occurred,?and?the?reason?is:?',?reason)
})
- 如果
onFulfilled或onRejected返回一個(gè)值x,那么.then返回的Promise實(shí)例的狀態(tài)會(huì)變成fulfilled,并且伴隨fulfilled的value會(huì)是x。注意,一個(gè)非Promise的普通值在被返回時(shí)會(huì)被Promise.resolve(x)包裝成為一個(gè)狀態(tài)為fulfilled的Promise實(shí)例。
new?Promise((resolve,?reject)?=>?{
????reject(2)
}).then(value?=>?{
????console.log('resolution?occurred,?and?the?value?is:?',?value)
},?reason?=>?{
????console.log('rejection?occurred,?and?the?reason?is:?',?reason)
????return?'a?new?value'
}).then(value?=>?{
????console.log('resolution?of?the?returned?promise?occurred,?and?the?value?is:?',?value)
},?reason?=>?{
????console.log('rejection?of?the?returned?promise?occurred,?and?the?reason?is:?',?reason)
})
- 如果
onFulfilled或onRejected中拋出一個(gè)異常,那么.then返回的Promise實(shí)例的狀態(tài)會(huì)變成rejected,并且伴隨rejected的reason是剛才拋出的異常的錯(cuò)誤對(duì)象e。
new?Promise((resolve,?reject)?=>?{
????resolve(1)
}).then(value?=>?{
????console.log('resolution?occurred,?and?the?value?is:?',?value)
????throw?new?Error('some?error?occurred.')
},?reason?=>?{
????console.log('rejection?occurred,?and?the?reason?is:?',?reason)
}).then(value?=>?{
????console.log('resolution?of?the?returned?promise?occurred,?and?the?value?is:?',?value)
},?reason?=>?{
????console.log('rejection?of?the?returned?promise?occurred,?and?the?reason?is:?',?reason)
})
- 如果
onFulfilled或onRejected返回一個(gè)Promise實(shí)例p2,那么不管p2的狀態(tài)是什么,.then返回的新Promise實(shí)例p1的狀態(tài)會(huì)取決于p2。如果p2現(xiàn)在或?qū)硎?code style="font-size:14px;background-color:rgba(27,31,35,.05);font-family:'Operator Mono', Consolas, Monaco, Menlo, monospace;color:rgb(19,148,216);">fulfilled,那么p1的狀態(tài)也隨之變成fulfilled,并且伴隨fulfilled的value也與p2進(jìn)行resolve(value)決議時(shí)傳遞的value相同;
new?Promise((resolve,?reject)?=>?{
????resolve(1)
}).then(value?=>?{
????console.log('resolution?occurred,?and?the?value?is:?',?value)
????return?Promise.resolve('a?fulfilled?promise')
},?reason?=>?{
????console.log('rejection?occurred,?and?the?reason?is:?',?reason)
}).then(value?=>?{
????console.log('resolution?of?the?returned?promise?occurred,?and?the?value?is:?',?value)
},?reason?=>?{
????console.log('rejection?of?the?returned?promise?occurred,?and?the?reason?is:?',?reason)
})
這個(gè)邏輯同樣適用于rejected的場(chǎng)景。也就是說,如果p2的狀態(tài)現(xiàn)在或?qū)硎?code style="font-size:14px;background-color:rgba(27,31,35,.05);font-family:'Operator Mono', Consolas, Monaco, Menlo, monospace;color:rgb(19,148,216);">rejected,那么p1的狀態(tài)也隨之變成rejected,而reason也來源于p1進(jìn)行reject(reason)決議時(shí)傳遞的reason。
new?Promise((resolve,?reject)?=>?{
????reject(1)
}).then(value?=>?{
????console.log('resolution?occurred,?and?the?value?is:?',?value)
},?reason?=>?{
????console.log('rejection?occurred,?and?the?reason?is:?',?reason)
????return?new?Promise((resolve,?reject)?=>?{
????????setTimeout(()?=>?{
????????????reject('a?promise?rejected?after?3?seconds.')
????????},?3000)
????})
}).then(value?=>?{
????console.log('resolution?of?the?returned?promise?occurred,?and?the?value?is:?',?value)
},?reason?=>?{
????console.log('rejection?of?the?returned?promise?occurred,?and?the?reason?is:?',?reason)
})
再考慮catch的行為catch的語法形式如下:
p.catch(onRejected);
.catch只會(huì)處理rejected的情況,并且也會(huì)返回一個(gè)新的Promise實(shí)例。
.catch(onRejected)與then(undefined, onRejected)在表現(xiàn)上是一致的。
事實(shí)上,catch(onRejected)從內(nèi)部調(diào)用了then(undefined, onRejected)。
- 如果
.catch(onRejected)的onRejected回調(diào)中返回了一個(gè)狀態(tài)為rejected的Promise實(shí)例,那么.catch返回的Promise實(shí)例的狀態(tài)也將變成rejected。
new?Promise((resolve,?reject)?=>?{
????reject(1)
}).catch(reason?=>?{
????console.log('rejection?occurred,?and?the?reason?is:?',?reason)
????return?Promise.reject('rejected')
}).then(value?=>?{
????console.log('resolution?of?the?returned?promise?occurred,?and?the?value?is:?',?value)
},?reason?=>?{
????console.log('rejection?of?the?returned?promise?occurred,?and?the?reason?is:?',?reason)
})
- 如果
.catch(onRejected)的onRejected回調(diào)中拋出了異常,那么.catch返回的Promise實(shí)例的狀態(tài)也將變成rejected。
new?Promise((resolve,?reject)?=>?{
????reject(1)
}).catch(reason?=>?{
????console.log('rejection?occurred,?and?the?reason?is:?',?reason)
????throw?2
}).then(value?=>?{
????console.log('resolution?of?the?returned?promise?occurred,?and?the?value?is:?',?value)
},?reason?=>?{
????console.log('rejection?of?the?returned?promise?occurred,?and?the?reason?is:?',?reason)
})
- 其他情況下,
.catch返回的Promise實(shí)例的狀態(tài)將是fulfilled。
不管一個(gè)Promise的狀態(tài)是fulfilled還是rejected,傳遞到finally方法的回調(diào)函數(shù)onFinally都會(huì)被執(zhí)行。我們可以把一些公共行為放在onFinally執(zhí)行,比如把loading狀態(tài)置為false。
注意,onFinally不會(huì)接受任何參數(shù),因?yàn)樗鼜脑O(shè)計(jì)上并不關(guān)心Promise實(shí)例的狀態(tài)是什么。
p.finally(function()?{
???//?settled?(fulfilled?or?rejected)
});
finally方法也會(huì)返回一個(gè)新的Promise實(shí)例,這個(gè)新的Promise實(shí)例的狀態(tài)也取決于onFinally的返回值是什么,以及onFinally中是否拋出異常。
你可以通過修改以下代碼中的注釋部分來驗(yàn)證,不同的返回值對(duì)于finally返回的Promise實(shí)例的狀態(tài)的影響。
new?Promise((resolve,?reject)?=>?{
????reject(1)
}).then(value?=>?{
????console.log('resolution?occurred,?and?the?value?is:?',?value)
},?reason?=>?{
????console.log('rejection?occurred,?and?the?reason?is:?',?reason)
????return?Promise.resolve(2);
????//?return?Promise.reject(3)
}).finally(()?=>?{
????//?return?Promise.resolve(4)
????//?return?Promise.reject(5)
????throw?new?Error('an?error')
}).then(value?=>?{
????console.log('resolution?of?the?returned?promise?occurred,?and?the?value?is:?',?value)
},?reason?=>?{
????console.log('rejection?of?the?returned?promise?occurred,?and?the?reason?is:?',?reason)
})
then, catch, finally小結(jié)綜合以上來看,不管是.then(onFulfilled, onRejected),還是.catch(onRejected),或者是.finally(onFinally),它們返回的Promise實(shí)例的狀態(tài)都取決于回調(diào)函數(shù)是否拋出異常,以及返回值是什么。
如果回調(diào)函數(shù)的返回值是一個(gè)狀態(tài)為
rejected的Promise實(shí)例,那么.then,.catch或.finally返回的Promise實(shí)例的狀態(tài)就是rejected。如果回調(diào)函數(shù)的返回值是一個(gè)還未決議的
Promise實(shí)例p2,那么.then,.catch或.finally返回的Promise實(shí)例p1的狀態(tài)取決于p2的決議結(jié)果。如果回調(diào)函數(shù)中拋出了異常,那么
.then,.catch或.finally返回的Promise實(shí)例的狀態(tài)就是rejected,并且reason是所拋出異常的對(duì)象e。其他情況下,
.then,.catch或.finally返回的Promise實(shí)例的狀態(tài)將是fulfilled。
由于.then會(huì)返回一個(gè)新的Promise實(shí)例,而在.then回調(diào)中拋出了異常,導(dǎo)致這個(gè)新Promise的狀態(tài)變成了rejected,而.catch正是用于處理這個(gè)新的Promise實(shí)例的rejected場(chǎng)景的。
new?Promise((resolve,?reject)?=>?{
????resolve(1)
}).then(value?=>?{
????console.log('resolution?of?the?returned?promise?occurred,?and?the?value?is:?',?value)
????var?a?=?b;?//?未定義b
}).catch(reason?=>?{
????console.log('caught?the?error?occured?in?the?callback?of?then?method,?and?the?reason?is:?',?reason)
})
最關(guān)鍵一點(diǎn)就是要理解:每次.then, .catch, .finally都產(chǎn)生一個(gè)新的Promise實(shí)例。
上文也提到了,.then, .catch, .finally都產(chǎn)生一個(gè)新的Promise實(shí)例,所以這種鏈?zhǔn)秸{(diào)用的對(duì)象實(shí)例已經(jīng)發(fā)生了變化。可以理解為:
Promise.prototype.then?=?function()?{
??//?balabala
??return?new?Promise((resolve,?reject)?=>?{
????//?if?balabala
????//?else?if?balabala
????//?else?balabala
??});
}
而jQuery鏈?zhǔn)秸{(diào)用是基于同一個(gè)jQuery實(shí)例的,可以簡單表述為:
jQuery.fn.css?=?function()?{
??//?balabala
??return?this;
}?? 感謝支持
本文主要是參考了MDN和《你不知道的JavaScript(下卷)》上關(guān)于Promise的知識(shí)點(diǎn),簡單分析了.then, .catch, .finally中回調(diào)函數(shù)的不同行為對(duì)于三者返回的Promise實(shí)例的影響,希望對(duì)大家有所幫助。
- 收藏吃灰不如現(xiàn)在就開始學(xué)習(xí),奧利給!
- 如果您覺得本文有所幫助,請(qǐng)留下您的點(diǎn)贊關(guān)注支持一波,謝謝!
- 快關(guān)注公眾號(hào)前端司南,與筆者一起交流學(xué)習(xí)吧!
