一、问题

今天突然被同事问到一个问题,以下代码怎么输出:

Promise.all([new Promise(res => res(0)), new Promise((res, rej) => rej(1))
]).then(v => {console.log('then 1 ====== ', v)
}).catch(e => {console.log('catch 1 ====== ', e)
})Promise.all([new Promise(res => res(0)), new Promise(res => res(1)),new Promise(res => res(2)), new Promise(res => res(3))
]).then(v => {console.log('then 2 ====== ', v)
}).catch(e => {console.log('catch 2 ====== ', e)
})

一眼看完,我果断回答了如下答案:

catch 1 ======  1
then 2 ======  [0, 1, 2, 3]

然而...翻了个大车,实际代码运行结果是:

then 2 ======  [0, 1, 2, 3]
catch 1 ======  1

一直以来都有个误解,以为then和catch的执行是一个二选一的过程,但这段代码颠覆了我的认知,这是为什么?它的执行过程是怎样的?Promise.all()方法的特点是输入的所有promise都完成则完成,有一个拒绝则拒绝,所以显然不是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-2

二、执行过程拆解

翻了一下MDN:catch

也就是说,catch是then的语法糖,obj.catch(fn) 内部其实是调用obj.then(undefined, fn)。

根据Promise/A+规范,Promise.prototype.then(arg1, arg2)两个参数都是可选,分为以下两种情况:

  • 如果promise状态是fullfilled,而arg1是undefined,那么arg1就相当于 x => x
  • 如果promise的状态是rejected,而arg2是undefined,那么arg2是一个throw error

Promise/A+规范:https://promisesaplus.com/

结合上面的解释,代码的执行过程为:

1、执行同步代码,解析Promise.reject().then(fn),满足情况二,相当于:

Promise.reject().then(() => {console.log('1-1')
}, () => {throw Error();
}).catch(() => {console.log('1-2')
})

我们知道then方法的返回值也是一个promise,此时假设调用Promise.reject().then(arg1, arg2)返回的promise为p1。

注意此时推入微任务队列的是arg2,也就是抛出错误的回调(Promise状态改变后,then中的回调才会被推入微任务队列)。之后p1又调用了catch,因为此时p1状态还没有被解决(p1处于pending状态),后面的catch设置的回调不会执行,而p1的状态要等到处理微任务队列时才会被解决。

假设微任务队列为一个数组,那么此时微任务队列中应该为:

[() => {throw Error()
}]

2、继续执行同步代码,解析Promise.resolve().then(fn),Promise状态为fulfilled,then中的回调fn被正常推入微任务队列。

Promise.resolve().then(() => {console.log('2-1')
}).catch(() => {console.log('2-2')
})

此时微任务队列中应该为:

[() => {throw Error()
},() => {console.log('2-1')
}]

3、同步代码执行完了,清空微任务队列中的任务:

  1. 取出第一个任务执行,抛出错误,相当于将p1的状态设置为rejected,那么此时后面的catch中的回调函数也被推入微任务队列

此时微任务队列为:

[() => {console.log('2-1')
},() => {console.log('1-2')
}]
  1. 取出第二个任务执行,打印2-1

此时微任务队列为:

[() => {console.log('1-2')
}]
  1. 取出第三个任务执行,打印1-2

此时微任务队列为:微任务队列清空。

[]

这个问题的关键就在于catch内部调用的还是obj.then(undefined, arg2),如果前一个promise的状态是rejected,并且then中第二个参数arg2是undefined,那么arg2是一个throw error,因此状态会顺延到Event Loop下一次Tick。

再看下面的例子:

Promise.reject().then(() => {console.log('1-1')
}).catch(() => {console.log('1-2')throw Error()
}).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中抛出了新的错误,所以又回到了前面出现的问题——后面的then中的arg2为throw error,因此状态再次发生顺延。

输出顺序为:2-1、1-2、1-4

当然,如果要按开头翻车的答案顺序执行,在确定前面的Promise状态为rejected时,不接then,直接调用catch即可。

Promise.reject().catch(() => {console.log('1-2')
})Promise.resolve().then(() => {console.log('2-1')
}).catch(() => {console.log('2-2')
})

输出顺序为:1-2、2-1

三、小结

看到这我想你已经明白大概是怎么回事了,总之,遇到问题稳住不要慌,文档 + 源码阅读能解决百分之八十以上的问题,一定要多思考,多理解,加油打工人~

四、参考

Promise中then和catch的执行过程

MDN:Promise.prototype.catch()

本文由博客一文多发平台 OpenWrite 发布!

Promise—关于catch(你真的了解catch的执行顺序吗)相关推荐

  1. java catch中 return_JAVA中try、catch、finally带return的执行顺序总结

    try catch  以后只要没有抛出新异常或者return,代码会继续往下执行的. 异常处理中,try.catch.finally的执行顺序,大家都知道是按顺序执行的.即,如果try中没有异常,则顺 ...

  2. java try、catch、finally及finally执行顺序详解

    1.为什么要用finally 先看一个没有finally的异常处理try-catch语句: 假设count为要使用到的资源,并且用完要求释放此资源.那么我们可以把释放资源的语句放到try-catch后 ...

  3. try { } catch{ } finally{ } 执行顺序总结

    publicclass TestTry { publicstaticvoid main(String[] args) { TestTry t = new TestTry(); System.out.p ...

  4. java try catch 异常后还会继续执行吗

    java try catch 异常后还会继续执行吗? catch 中如果你没有再抛出异常 , 那么catch之后的代码是可以继续执行的 , 但是try中 , 报错的那一行代码之后 一直到try结束为止 ...

  5. java try catch 例子_java try catch

    try catch机制非常好.那些觉得try catch不行的人,是他们自己的水平有问题,无法理解这种机制.并且这群人写代码不遵守规则,喜欢偷懒,这才造成try catch不好的错觉. 详细解释: 1 ...

  6. java catch throwable_如何处理异常? catch Exception OR catch Throwable

    在Java中,当你需要统一处理异常的时候,你是会选择catch (Exception),还是直接catch (Throwable)? Java的异常体系 Throwable: Java中所有异常和错误 ...

  7. try catch嵌套执行顺序测试

    分享一段测试try catch 执行顺序和那些代码后续执行,那么不执行的代码: @Testpublic void testTryCatch(){try {System.out.println(&quo ...

  8. java catch 抛出异常_java - 在catch和最后claus中抛出异常

    java - 在catch和最后claus中抛出异常 关于大学的Java问题,有这段代码: class MyExc1 extends Exception {} class MyExc2 extends ...

  9. 细琢磨,try catch finally 执行顺序与返回值

    try catch finally 常见格式如下: try{//应用代码}catch(Exception e){//异常捕捉处理}finally{//资源释放.流关闭等等 } 通常执行顺序: try有 ...

最新文章

  1. HDU_2112 HDU Today(DIjkstra + map映射)
  2. 华人一作统一「视觉-语言」理解与生成:一键生成图像标注,完成视觉问答,Demo可玩...
  3. 笔面集锦:判断单链表里面是否有环及相关扩展题
  4. POJ 1806 Manhattan 2025
  5. POJ2553 强连通出度为0的应用
  6. 图像处理与计算机视觉:基础,经典以及最近发展(5)计算机视觉
  7. 频谱仪使用方法图解_地暖分水器原理及使用方法介绍,图解
  8. boost::process::extend相关的测试程序
  9. 如何在一年内从零基础到前端就业?
  10. 数据库主体在该数据库中拥有架构,无法删除解决方法(转)
  11. QT 中控件内坐标转换为父窗口坐标或屏幕坐标
  12. ORA-03113: 通信通道的文件结尾
  13. python2.7+pyqt4安装
  14. Excel技巧—快速插入空行技巧大集合
  15. spire.office for.net 的Crack
  16. web哪里有php文件,web文件管理器
  17. h3c交换机配置远程管理_H3C 交换机设置本地用户和telnet远程登录配置 v7 版本...
  18. 依赖包存在,但是仍依然报找不到包的问题
  19. Oracle数据库基本使用
  20. php版本kms,通过 AWS KMS API 和 AWS SDK for PHP 版本 3 使用别名 - 适用于 PHP 的 AWS 开发工具包...

热门文章

  1. 微信小程序调用unicloud云函数的方法
  2. linux卷查看命令,[命令] Linux 命令组 lvm(逻辑卷管理)
  3. 信号线等串联的小电阻的作用
  4. 今日早报 每日精选12条新闻简报 每天一分钟 知晓天下事 6月4日
  5. 严重: Context [] startup failed due to previous errors
  6. 求出首地址为DATA的字数组中的最小偶数,并将它放在AX中
  7. 谈谈如何建立数据模型:
  8. node项目(一) koa脚手架的搭建
  9. 【Flink】迟到数据的处理
  10. C/C++ 获取系统环境变量的方法!看完让你灵光一闪!