最近在看Promise相關(guān)知識(shí),遇到一個(gè)面試題,以我的理解,應(yīng)該是先輸出a failed,然后b failed b passed的,可是為什么a failed在中間輸出了??
catch
本質(zhì)也是 Promise.prototype.then
的封裝,所以 a 相當(dāng)于跳過(guò)了一輪循環(huán),整個(gè)過(guò)程可以這么理解
reject('a')
reject('b')
(next turn)
reject('a') -> handle(onReject) 沒(méi) handler,傳遞下去
reject('b') -> handle(onReject) 這里被 catch 處理
(next turn)
reject('a') -> handle(onReject) -> handle(onReject) 這里被 catch 處理
reject('b') -> handle(onReject) -> handle(onFullfill)
不要被鏈?zhǔn)秸{(diào)用迷惑了。
let a1 = Promise.reject('a')
let a2 = a1.then(() => {
console.log('a passed')
})
let a3 = a2.catch(() => {
console.log('a failed')
})
let b1 = Promise.reject('b')
let b2 = b1.catch(() => {
console.log('b failed')
})
let b3 = b2.then(() => {
console.log('b passed')
})
不知道這樣你能懂嗎?鏈?zhǔn)秸{(diào)用的then()和catch()處理的不是同一個(gè)promise。
而未處理的狀態(tài)會(huì)傳遞下去。
注意:如果忽略針對(duì)某個(gè)狀態(tài)的回調(diào)函數(shù)參數(shù),或者提供非函數(shù) (nonfunction) 參數(shù),那么 then 方法將會(huì)丟失關(guān)于該狀態(tài)的回調(diào)函數(shù)信息,但是并不會(huì)產(chǎn)生錯(cuò)誤。如果調(diào)用 then 的 Promise 的狀態(tài)(fulfillment 或 rejection)發(fā)生改變,但是 then 中并沒(méi)有關(guān)于這種狀態(tài)的回調(diào)函數(shù),那么 then 將創(chuàng)建一個(gè)沒(méi)有經(jīng)過(guò)回調(diào)函數(shù)處理的新 Promise 對(duì)象,這個(gè)新 Promise 只是簡(jiǎn)單地接受調(diào)用這個(gè) then 的原 Promise 的終態(tài)作為它的終態(tài)。
什么意思呢,then的第二個(gè)參數(shù)其實(shí)是能處理err的,但是沒(méi)定義的話,就會(huì)將上一個(gè)promise的狀態(tài)當(dāng)做當(dāng)前then創(chuàng)建的返回值promise的狀態(tài)傳遞下去。
強(qiáng)調(diào):Promise
的每個(gè)then
或catch
都是異步執(zhí)行的。
因此,實(shí)際上最先執(zhí)行的是a.then
,但沒(méi)有定義catch
,所以拋出異常,然后異步交給后面的catch
處理(a failed
)。此時(shí)下一個(gè)等待執(zhí)行的是b.catch
(b failed
),處理完之后,同樣異步交給后面的then
(b passed
)。接著,之前排隊(duì)的catch
(b failed
)執(zhí)行,最后b passed
執(zhí)行。
這就是各個(gè)then/catch
交替執(zhí)行的原因。
整個(gè)過(guò)程類似于下面的代碼:
setTimeout(function(){
console.log(1);
setTimeout(function(){
console.log(2);
}, 0);
}, 0);
setTimeout(function(){
console.log(3);
setTimeout(function(){
console.log(4);
}, 0);
}, 0);
結(jié)果打印1 3 2 4
,而不是1 2 3 4
。
把代碼換個(gè)形式看,
let p1 = Promise.reject('a') // p1 rejected
let p2 = p1.then(function cb1 () {log('a passed')}) // then 未指定 onrejected;p2 pending
let p3 = p2.catch(function cb2 (){log('a failed')}) // p3 pending,且要等 p2 settled
let p4 = Promise.reject('b') // p4 rejected
let p5 = p4.catch(function cb3 (){log('b failed')}) // p5 pending
let p6 = p5.then(function cb4 (){log('b passed')}) // p6 pending,要等 p5 settled
p1 的狀態(tài)是 rejected, 而 cb1 對(duì)應(yīng)的是 onfullfilled,所以沒(méi)機(jī)會(huì)進(jìn)入 queue
所以 event loop 中 queue 的狀態(tài)是
第一輪: [cb3]
第二輪: [cb2, cb4]
所以 b failed -> a failed -> b passed
解題關(guān)鍵:
第一:
Promise.reject(reason)
Promise.resolve(value)
Promise.prototype.catch(onRejected)
Promise.prototype.then(onFulfilled, onRejected)
Promise.prototype.finally(onFinally)
前2個(gè)是靜態(tài)方法,后面3個(gè)是原型方法(對(duì)象調(diào)用),他們的共同點(diǎn)就是都是返回一個(gè)新的promise對(duì)象。
第二:
onRejected,onFulfilled,onFinally 這個(gè)3個(gè)稱為executor 函數(shù),分別處理 promise的狀態(tài)
1. onRejected 處理 Rejected
2. onFulfilled 處理 Fulfilled
3. onFinally 處理 Rejected或者Fulfilled
那么問(wèn)題來(lái)了,當(dāng)這個(gè)3個(gè)執(zhí)行函數(shù)缺失的時(shí)候,Promise怎么處理呢?示例代碼如下
Promise.reject(a)
.then()
.then()
.catch()
.finally(()=>{
console.log('test') // 會(huì)輸出嗎?會(huì)報(bào)錯(cuò)嗎?很顯然,不會(huì)報(bào)錯(cuò),因?yàn)檫@些方法都會(huì)返回新的promise對(duì)象
})
猜測(cè)下,這個(gè)3個(gè)方法的底層實(shí)現(xiàn),會(huì)不會(huì)是這樣
Promise.prototype.then = function(onFulfilled,onRejected,resutl){
return new Promise((resolve,reject)=>{
if(typeof onFulfilled === 'function'){
resolve(onFulfilled.call(this,resutl));
}else{
resolve(resutl);
}
if(typeof onRejected === 'function'){
reject(onRejected.call(this,resutl));
}else{
reject(resutl);
}
});
}
所以這樣,就好理解這個(gè)題目的輸出了
第一個(gè)catch的執(zhí)行函數(shù)執(zhí)行,需要等第一個(gè)then的執(zhí)行函數(shù)執(zhí)行(雖然缺失,但是包裝函數(shù)還是有的),而這個(gè)then的執(zhí)行,需要等到Promise.reject() 完成 2
第二個(gè)catch的執(zhí)行函數(shù)執(zhí)行,需要等到Promise.reject() 完成。1
第二個(gè)then執(zhí)行函數(shù)的執(zhí)行,需要等到第二個(gè)catch的執(zhí)行函數(shù)執(zhí)行,第二個(gè)catch的執(zhí)行需要等到Promise.reject() 完成。2
同時(shí)由于代碼的執(zhí)行先后的原因 所以第一個(gè) 2 會(huì)在第二個(gè)2 的前面,因此最終的執(zhí)行順序就是 1 2 2(第一個(gè))(第二個(gè))
修改上面的代碼,可以很容易實(shí)現(xiàn)不同的輸出方式。因?yàn)槊總€(gè) 執(zhí)行函數(shù)的執(zhí)行,都是需要等到自身promise對(duì)象狀態(tài)發(fā)生變化才會(huì)去做的。
Promise.reject('a')
.then(success=>{
},err=>{
return new Promise((resolve,reject)=>{
})
})
.catch(e=>{
console.log('a failed') //這個(gè)永遠(yuǎn)不會(huì)執(zhí)行
})
Promise.reject('b')
.catch(e=>{
console.log('b ...')
})
.then(success=>{
console.log('b then')
})
按照 event loop去理解也是可以,其實(shí)這也是異步的本質(zhì)。如果按照 執(zhí)行函數(shù)的執(zhí)行一定是promise狀態(tài)發(fā)生變化了才會(huì)觸發(fā),這樣理解會(huì)不會(huì)更好
北大青鳥(niǎo)APTECH成立于1999年。依托北京大學(xué)優(yōu)質(zhì)雄厚的教育資源和背景,秉承“教育改變生活”的發(fā)展理念,致力于培養(yǎng)中國(guó)IT技能型緊缺人才,是大數(shù)據(jù)專業(yè)的國(guó)家
北大青鳥(niǎo)中博軟件學(xué)院創(chuàng)立于2003年,作為華東區(qū)著名互聯(lián)網(wǎng)學(xué)院和江蘇省首批服務(wù)外包人才培訓(xùn)基地,中博成功培育了近30000名軟件工程師走向高薪崗位,合作企業(yè)超4
中公教育集團(tuán)創(chuàng)建于1999年,經(jīng)過(guò)二十年潛心發(fā)展,已由一家北大畢業(yè)生自主創(chuàng)業(yè)的信息技術(shù)與教育服務(wù)機(jī)構(gòu),發(fā)展為教育服務(wù)業(yè)的綜合性企業(yè)集團(tuán),成為集合面授教學(xué)培訓(xùn)、網(wǎng)
達(dá)內(nèi)教育集團(tuán)成立于2002年,是一家由留學(xué)海歸創(chuàng)辦的高端職業(yè)教育培訓(xùn)機(jī)構(gòu),是中國(guó)一站式人才培養(yǎng)平臺(tái)、一站式人才輸送平臺(tái)。2014年4月3日在美國(guó)成功上市,融資1
曾工作于聯(lián)想擔(dān)任系統(tǒng)開(kāi)發(fā)工程師,曾在博彥科技股份有限公司擔(dān)任項(xiàng)目經(jīng)理從事移動(dòng)互聯(lián)網(wǎng)管理及研發(fā)工作,曾創(chuàng)辦藍(lán)懿科技有限責(zé)任公司從事總經(jīng)理職務(wù)負(fù)責(zé)iOS教學(xué)及管理工作。
浪潮集團(tuán)項(xiàng)目經(jīng)理。精通Java與.NET 技術(shù), 熟練的跨平臺(tái)面向?qū)ο箝_(kāi)發(fā)經(jīng)驗(yàn),技術(shù)功底深厚。 授課風(fēng)格 授課風(fēng)格清新自然、條理清晰、主次分明、重點(diǎn)難點(diǎn)突出、引人入勝。
精通HTML5和CSS3;Javascript及主流js庫(kù),具有快速界面開(kāi)發(fā)的能力,對(duì)瀏覽器兼容性、前端性能優(yōu)化等有深入理解。精通網(wǎng)頁(yè)制作和網(wǎng)頁(yè)游戲開(kāi)發(fā)。
具有10 年的Java 企業(yè)應(yīng)用開(kāi)發(fā)經(jīng)驗(yàn)。曾經(jīng)歷任德國(guó)Software AG 技術(shù)顧問(wèn),美國(guó)Dachieve 系統(tǒng)架構(gòu)師,美國(guó)AngelEngineers Inc. 系統(tǒng)架構(gòu)師。