原贴链接:javascript异步之Promise then和catch

这是javascript异步系列文章的第六篇

前面介绍了Promise.all()、Promise.race()、Promise.finally()、resolve()、reject() 今天讨论一下then和catch

我们展开promise的原型链,看看有哪些实例方法

  1. const p1 = new Promise((resolve, reject) => {
  2. })console.log(p1.__proto__);

image.png

p1是Promise的一个实例,我们展开p1的原型链,可以看到

p1具有then方法,catch方法,还有finally方法(这个方法我们之前讨论过)

也就是说,这些方法是定义在原型对象Promise.prototype上的。

then方法

它的作用是为 Promise 实例添加状态改变时的回调函数。then方法可以有两个参数(第二个可选),

第一个是resolve状态的回调函数第二个是reject状态的回调函数(可选)

then方法返回的是一个新的Promise实例(不是原来那个Promise实例)

我们前面说过Promise接收一个函数作为参数,这个函数有两个参数,第二个参数可选,分别是resolve和reject,这两个参数分别和then方法的两个参数对应。

then方法其实并不简单,我们看几个栗子 观察如下代码,想一想最后输出什么?

一个经典栗子

  1. Promise.reject('出错啦')
  2. .then(res => console.log(`res1:${res}`), err => console.log(`err1:${err}`))
  3. .then(res => console.log(`res2:${res}`), err => console.log(`err2:${err}`));

不卖关子,直接告诉你答案:

  1. //=>err1:出错啦//=>res2:undefined

可能和你想的不一样,或者看着输出反推,也没找出个所以然, 我们一起分析一下原因

Promise.reject()方法也会返回一个新的 Promise 实例,该实例的状态为rejected。 以下两种写法等价

  1. const p1 = Promise.reject('出错啦')
  2. //和下面的方法等效
  3. const p2 = new Promise((resolve, reject) => { reject('出错啦')})

resolve和reject当传入一个字符串作为参数时,则会返回一个新的Promise实例

如果是resolve,新的Promise对象的状态为resolve

如果是reject,新的Promise对象的状态为reject

上面的p1 和p2 都会返回一个新的Promise实例,并且状态为reject

  1. const p1 = Promise.reject('出错啦')
  2. p1.then(res => {
  3. console.log(`res1:${res}`)//“这里不执行”
  4. },
  5. err => {
  6. console.log(`err1:${err}`)//=>err1:出错啦
  7. })
  8. //最后输出err1:出错啦

因为p1返回的Promise实例状态为reject,所以执行了then下面第二个参数的方法

到这里可能会有一个疑问,then方法的第二个参数可选,在这里如果不用第二个参数会怎么样呢?

  1. const p1 = Promise.reject('出错啦')p1
  2. .then(res => {
  3. console.log(`res1:${res}`)})//
  4. .catch(err=>{
  5. // console.log(`err:${err}`)//
  6. })
  7. //=>Uncaught (in promise) 出错啦

会报一个未捕获的错,当省略第二个参数时,catch的存在就很有意义 ,关于catch我们稍后讨论

上面对第一个then的输出做了讨论,我们继续看第二then 在解释第二个then之前,我们先看另一个栗子

关于返回值

  1. const p3 = new Promise((resolve, reject) => {
  2. reject('出错啦');
  3. })
  4. //第一个then
  5. .then(
  6. res => {
  7. console.log(`resolve-then1${res}`);//“不执行这里”
  8. return 1;
  9. }, err => {
  10. console.log(`reject-then1${err}`);//=>reject-then1:出错啦
  11. return 1
  12. })
  13. //第二个then
  14. .then(res => {
  15. console.log(`resolve-then2${res}`);//=>resolve-then2:1
  16. throw new Error('抛出一个错')
  17. }, err => {
  18. console.log(`reject-then2${err}`);//“不执行这里”
  19. throw new Error('抛出一个错')
  20. })
  21. //第三个then
  22. .then(res => {
  23. console.log(`resolve-then3${res}`);//“不执行这里”
  24. }, err => {
  25. console.log(`reject-then3${err}`);//=>reject-then3:Error: 抛出一个错
  26. })
  27. //第四个then
  28. .then(res => {
  29. console.log(`resolve-then4${res}`);//=>resolve-then4:undefined
  30. }, err => {
  31. console.log(`reject-then4${err}`);//“不执行这里”
  32. })
  33. //第五个then
  34. .then(res => {
  35. console.log(`resolve-then5${res}`);//=>resolve-then5:undefined
  36. }, err => {
  37. console.log(`reject-then5${err}`);//“不执行这里”
  38. })

输出,具体位置,参见上面的代码

  1. //=>reject-then1:出错啦
  2. //=>resolve-then2:1
  3. //=>reject-then3:Error: 抛出一个错
  4. //=>resolve-then4:undefined
  5. //=>resolve-then5:undefined

上面罗列了我能找到的所有返回值的情况 在MDN上关于返回值的介绍,解释的有点绕

image.png

我们分析一下p3

p3直接执行的reject(‘出错啦’),返回的是一个promise对象,状态为reject

所以第一个then执行的第二个参数方法,输出=>reject-then1:出错啦,然后return 1

我们看第二个then的输出,可以发现,第一个then的return值会作为下第二个then的回调函数的参数值,第二个then又执行了throw Error

第二个then的throw Error,使得第三个then下的Promise对象状态为reject,所以第三个then输出=>reject-then3:Error: 抛出一个错

第三个then没有返回值,也没有执行throw Error,结果导致第四个then输出=>resolve-then4:undefined

第四个then和第三个then一样,没有返回值,所以第五个then输出的结果和第四个一样=>resolve-then5:undefined

我们做一下总结

如果前一个Promise对象是resolve状态,则后一个Promise对象执行第一个参数方法(resolve)如果前一个Promise对象是reject状态,则后一个Promise对象执行第二个参数方法(reject)如果前一个Promise对象抛出异常(throw error),则后一个Promise对象执行第二个参数方法(reject)如果前一个Promise对象返回具体的值,则此数值将作为后一个Promise对象的输入,执行第一个参数方法(resolve)如果前一个Promise对象没有返回状态(resolve或者reject),也没有抛错(throw error),也没有返回具体数值,我们则认为它返回 了一个undefined,则undefined将作为后一个Promise对象的输入,执行第一个参数方法(resolve)

关于p3 我们介绍到这里,回头看一下前面的栗子

  1. Promise.reject('出错啦')
  2. .then(res => console.log(`res1:${res}`), err => console.log(`err1:${err}`))
  3. .then(res => console.log(`res2:${res}`), err => console.log(`err2:${err}`));

现在看来似乎简单了 因为前面是reject状态,所以第一个then执行第二个参数方法 “err => console.log(err1:${err})”

因为第一个then方法,没有返回状态,没有抛错,没有返会具体值,所以返回的是undefined,第二个then执行 “res => console.log(res2:${res}

catch

花了大量篇幅介绍then方法,其实then方法懂了,catch自然也就明白了,因为,catch就是then方法的语法糖

catch方法是.then(null, rejection)或.then(undefined, rejection)的别名

也就是说,catch也是then,它用于捕获错误,它的参数也就是then的第二个参数。

我们将上面的栗子做一下改动

  1. const p4 = new Promise((resolve, reject) => {
  2. reject('出错啦');
  3. })
  4. .catch(err => {
  5. console.log(`catch1${err}`);//=>catch1:出错啦
  6. return 1;
  7. })
  8. .then(res => {
  9. console.log(`then1${res}`);//=>then1:1
  10. throw new Error('抛出一个错')
  11. })
  12. .catch(err => {
  13. console.log(`catch2${err}`);//=>catch2:Error: 抛出一个错
  14. })
  15. .then(res => {
  16. console.log(`then2${res}`);//=>then2:undefined
  17. })

如果上面的关于then的介绍看懂了,这个自然就会明白

关于promise的then和catch就介绍这么多

老实说 这章,我没看明白