1.今天突然被同事问到的问题。如何输出如下代码:Promise.all([newPromise(res=>res(0)),newPromise((res,rej)=>rej(1))]).then(v=>{console.log('then1======',v)}).catch(e=>{console.log('catch1======',e)})Promise.all([newPromise(res=>res(0)),newPromise(res=>res(1)),newPromise(res=>res(2)),newPromise(res=>res(3))]).then(v=>{console.log('then2======',v)}).catch(e=>{console.log('catch2======',e)})之后看了一眼,果断回答如下:catch1======1then2======[0,1,2,3]然而...翻了个大车,实际代码运行结果为:then2======[0,1,2,3]catch1======1一直有一个误区认为then和catch的执行是一个在两者之间选择的过程,但是这段代码颠覆了我的认知,为什么呢?它的执行过程是怎样的?Promise.all()方法的特点是所有输入的promise,如果fulfill了就完成,如果有reject了就reject,所以显然不是Promise.all()的问题。其实上面的例子可以简化成下面的代码,下面的分析会以下面的代码为例Promise.reject().then(()=>{console.log('1-1')}).catch(()=>{console.log('1-2')})Promise.resolve().then(()=>{console.log('2-1')}).catch(()=>{console.log('2-2')})这段代码的执行结果为:2-1,1-22.执行过程反汇编翻了MDN:catch也就是catch是then的语法糖,obj.catch(fn)实际上在内部调用了obj.then(undefined,fn)。根据Promise/A+规范,Promise.prototype.then(arg1,arg2)的两个参数是可选的,可以分为以下两种情况:如果promise状态为fullfilled且arg1未定义,则arg1等价tox=>x如果promise的状态是rejected,arg2是undefined,那么arg2就是一个throwerrorPromise/A+规范:https://promisesaplus.com/结合上面的解释,代码执行过程是:1.执行同步代码并解析Promise.reject().then(fn),满足第二个条件,等价于:Promise.reject().then(()=>{console.log('1-1')},()=>{throwError();}).catch(()=>{console.log('1-2')})我们知道then方法的返回值也是一个promise。此时假设调用Promise.reject().then(arg1,arg2)返回p1的promise。注意此时arg2被压入微任务队列,也就是抛出错误的回调(then中的回调只有在Promise状态发生变化后才会被压入微任务队列)。之后p1再次调用catch,因为此时p1的状态还没有被解析(p1处于pending状态),后面的catch设置的回调不会执行,p1的状态也不会被解析直到处理完微任务队列。假设微任务队列是一个数组,那么微任务队列应该是:[()=>{throwError()}]2.继续执行同步代码,解析Promise.resolve().then(fn),Promise状态为fulfill,then中的回调fn正常被推入微任务队列。Promise.resolve().then(()=>{console.log('2-1')}).catch(()=>{console.log('2-2')})此时微任务队列应该是:[()=>{throwError()},()=>{console.log('2-1')}]3、同步代码执行完后,清空微任务队列中的任务:takeout第一个task执行完抛出错误,相当于把p1的状态设置为rejected,那么后面catch中的回调函数也被推入microtask队列。此时微任务队列为:[()=>{console.log('2-1')},()=>{console.log('1-2')}]取出第二个任务执行,打印2-1,此时微任务队列为:[()=>{console.log('1-2')}]取出第三个任务执行,打印1-2。此时microtask队列为:microtask队列清空。[]这个问题的关键是在catch里面调用了obj.then(undefined,arg2)。如果之前的promise的状态是rejected,并且then中的第二个参数arg2是undefined,那么arg2就是一个throwerror,所以状态会被推迟到EventLoop的下一个Tick。再看下面的例子:Promise.reject().then(()=>{console.log('1-1')}).catch(()=>{console.log('1-2')throwError()}).then(()=>{console.log('1-3')}).catch(()=>{console.log('1-4')})Promise.resolve().then(()=>{console.log('2-1')}).catch(()=>{console.log('2-2')})不设置返回值调用catch方法默认情况下,它还会返回一个状态为fulfilled的promise。一般情况下会使用后面设置的then回调,但是由于我们这里的catch中抛出了一个新的错误,所以又回到了之前的问题——后面的thenarg2中是throwerror,所以状态又被推迟了。输出顺序为:2-1、1-2、1-4。当然,如果你想按照一开始回答的顺序执行,当你确认之前的Promise状态为rejected时,可以直接调用catch而不是then。Promise.reject().catch(()=>{console.log('1-2')})Promise.resolve().then(()=>{console.log('2-1')}).catch(()=>{console.log('2-2')})输出顺序为:1-2,2-13.总结看到这里,我想你已经明白是怎么回事了。总之,遇到问题不要慌张。文档+源码阅读可以解决80%以上的问题。一定要多思考,多理解,多努力~4.参考PromiseMDN中then和catch的执行过程:Promise.prototype.catch()本文由博文发布平台OpenWrite发布!
