求解Promise的一道面试题

最近在看Promise相关知识,遇到一个面试题,以我的理解,应该是先输出a failed,然后b failed b passed的,可是为什么a failed在中间输出了??
图片描述

阅读 4.4k
6 个回答

强调:Promise的每个thencatch都是异步执行的。

因此,实际上最先执行的是a.then,但没有定义catch,所以抛出异常,然后异步交给后面的catch处理(a failed)。此时下一个等待执行的是b.catchb failed),处理完之后,同样异步交给后面的thenb passed)。接着,之前排队的catchb failed)执行,最后b passed执行。

这就是各个then/catch交替执行的原因。

整个过程类似于下面的代码:

setTimeout(function(){
    console.log(1);
    setTimeout(function(){
        console.log(2);
    }, 0);
}, 0);

setTimeout(function(){
    console.log(3);
    setTimeout(function(){
        console.log(4);
    }, 0);
}, 0);

结果打印1 3 2 4,而不是1 2 3 4

把代码换个形式看,

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 的状态是 rejected, 而 cb1 对应的是 onfullfilled,所以没机会进入 queue

所以 event loop 中 queue 的状态是
第一轮: [cb3]
第二轮: [cb2, cb4]

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

catch 本质也是 Promise.prototype.then 的封装,所以 a 相当于跳过了一轮循环,整个过程可以这么理解

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)

不要被链式调用迷惑了。

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')
})

不知道这样你能懂吗?链式调用的then()和catch()处理的不是同一个promise。
而未处理的状态会传递下去。

mdn上有解释的

注意:如果忽略针对某个状态的回调函数参数,或者提供非函数 (nonfunction) 参数,那么 then 方法将会丢失关于该状态的回调函数信息,但是并不会产生错误。如果调用 then 的 Promise 的状态(fulfillment 或 rejection)发生改变,但是 then 中并没有关于这种状态的回调函数,那么 then 将创建一个没有经过回调函数处理的新 Promise 对象,这个新 Promise 只是简单地接受调用这个 then 的原 Promise 的终态作为它的终态。

什么意思呢,then的第二个参数其实是能处理err的,但是没定义的话,就会将上一个promise的状态当做当前then创建的返回值promise的状态传递下去。

解题关键:
第一:

Promise.reject(reason)
Promise.resolve(value)
Promise.prototype.catch(onRejected)
Promise.prototype.then(onFulfilled, onRejected)
Promise.prototype.finally(onFinally)
前2个是静态方法,后面3个是原型方法(对象调用),他们的共同点就是都是返回一个新的promise对象。

第二:

onRejected,onFulfilled,onFinally 这个3个称为executor 函数,分别处理 promise的状态

1. onRejected 处理 Rejected
2. onFulfilled 处理 Fulfilled
3. onFinally 处理 Rejected或者Fulfilled

那么问题来了,当这个3个执行函数缺失的时候,Promise怎么处理呢?示例代码如下
Promise.reject(a)
.then()
.then()
.catch()
.finally(()=>{
   console.log('test')  // 会输出吗?会报错吗?很显然,不会报错,因为这些方法都会返回新的promise对象
})

猜测下,这个3个方法的底层实现,会不会是这样

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);
    }
   });
}

所以这样,就好理解这个题目的输出了
第一个catch的执行函数执行,需要等第一个then的执行函数执行(虽然缺失,但是包装函数还是有的),而这个then的执行,需要等到Promise.reject() 完成 2
第二个catch的执行函数执行,需要等到Promise.reject() 完成。1
第二个then执行函数的执行,需要等到第二个catch的执行函数执行,第二个catch的执行需要等到Promise.reject() 完成。2
同时由于代码的执行先后的原因 所以第一个 2 会在第二个2 的前面,因此最终的执行顺序就是 1 2 2(第一个)(第二个)

修改上面的代码,可以很容易实现不同的输出方式。因为每个 执行函数的执行,都是需要等到自身promise对象状态发生变化才会去做的。

Promise.reject('a')
.then(success=>{
},err=>{
   return new Promise((resolve,reject)=>{
    
    })
})
.catch(e=>{
    console.log('a failed') //这个永远不会执行
})

Promise.reject('b')
.catch(e=>{
    console.log('b ...')
})
.then(success=>{
    console.log('b then')
})

按照 event loop去理解也是可以,其实这也是异步的本质。如果按照 执行函数的执行一定是promise状态发生变化了才会触发,这样理解会不会更好

示例代码

此句是错的 catch对于已经完成的promise同步执行 此句是错的

then异步执行(类型setTimeout(xxx,0))

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题