鍍金池/ 問答/C#  HTML/ 求解Promise的一道面試題

求解Promise的一道面試題

最近在看Promise相關(guān)知識(shí),遇到一個(gè)面試題,以我的理解,應(yīng)該是先輸出a failed,然后b failed b passed的,可是為什么a failed在中間輸出了??
圖片描述

回答
編輯回答
終相守

catch 本質(zhì)也是 Promise.prototype.then 的封裝,所以 a 相當(dāng)于跳過了一輪循環(huán),整個(gè)過程可以這么理解

reject('a')
reject('b')

(next turn)

reject('a') -> handle(onReject) 沒 handler,傳遞下去
reject('b') -> handle(onReject) 這里被 catch 處理

(next turn)

reject('a') -> handle(onReject) -> handle(onReject) 這里被 catch 處理
reject('b') -> handle(onReject) -> handle(onFullfill)
2017年4月26日 06:41
編輯回答
逗婦乳

此句是錯(cuò)的 catch對(duì)于已經(jīng)完成的promise同步執(zhí)行 此句是錯(cuò)的

then異步執(zhí)行(類型setTimeout(xxx,0))

2018年9月21日 21:25
編輯回答
奧特蛋

不要被鏈?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ì)傳遞下去。

mdn上有解釋的

注意:如果忽略針對(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 中并沒有關(guān)于這種狀態(tài)的回調(diào)函數(shù),那么 then 將創(chuàng)建一個(gè)沒有經(jīng)過回調(diào)函數(shù)處理的新 Promise 對(duì)象,這個(gè)新 Promise 只是簡單地接受調(diào)用這個(gè) then 的原 Promise 的終態(tài)作為它的終態(tài)。

什么意思呢,then的第二個(gè)參數(shù)其實(shí)是能處理err的,但是沒定義的話,就會(huì)將上一個(gè)promise的狀態(tài)當(dāng)做當(dāng)前then創(chuàng)建的返回值promise的狀態(tài)傳遞下去。

2018年8月4日 16:42
編輯回答
葬愛

強(qiáng)調(diào):Promise的每個(gè)thencatch都是異步執(zhí)行的。

因此,實(shí)際上最先執(zhí)行的是a.then,但沒有定義catch,所以拋出異常,然后異步交給后面的catch處理(a failed)。此時(shí)下一個(gè)等待執(zhí)行的是b.catchb failed),處理完之后,同樣異步交給后面的thenb passed)。接著,之前排隊(duì)的catchb failed)執(zhí)行,最后b passed執(zhí)行。

這就是各個(gè)then/catch交替執(zhí)行的原因。

整個(gè)過程類似于下面的代碼:

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。

2018年1月16日 09:22
編輯回答
憶往昔

把代碼換個(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,所以沒機(jī)會(huì)進(jìn)入 queue

所以 event loop 中 queue 的狀態(tài)是
第一輪: [cb3]
第二輪: [cb2, cb4]

所以 b failed -> a failed -> b passed

2017年9月15日 12:41
編輯回答
檸檬藍(lán)

解題關(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

那么問題來了,當(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ì)象
})

猜測下,這個(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ì)更好

示例代碼

2018年4月24日 18:21