陌上寒

陌上寒个人博客

javascript异步之Promise then和catch

这是javascript异步系列文章的第六篇
前面介绍了Promise.all()、Promise.race()、Promise.finally()、resolve()、reject()
今天讨论一下then和catch
我们展开promise的原型链,看看有哪些实例方法

const p1 = new Promise((resolve, reject) => {

})
console.log(p1.__proto__);

《javascript异步之Promise then和catch》
p1是Promise的一个实例,我们展开p1的原型链,可以看到
p1具有then方法,catch方法,还有finally方法(这个方法我们之前讨论过)
也就是说,这些方法是定义在原型对象Promise.prototype上的。

then方法

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

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

then方法返回的是一个新的Promise实例(不是原来那个Promise实例)我们前面说过Promise接收一个函数作为参数,这个函数有两个参数,第二个参数可选,分别是resolve和reject,这两个参数分别和then方法的两个参数对应。
then方法其实并不简单,我们看几个栗子
观察如下代码,想一想最后输出什么?

一个经典栗子

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

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

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

可能和你想的不一样,或者看着输出反推,也没找出个所以然,
我们一起分析一下原因
Promise.reject()方法也会返回一个新的 Promise 实例,该实例的状态为rejected。
以下两种写法等价

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

resolve和reject当传入一个字符串作为参数时,则会返回一个新的Promise实例
如果是resolve,新的Promise对象的状态为resolve
如果是reject,新的Promise对象的状态为reject
上面的p1 和p2 都会返回一个新的Promise实例,并且状态为reject

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

因为p1返回的Promise实例状态为reject,所以执行了then下面第二个参数的方法
到这里可能会有一个疑问,then方法的第二个参数可选,在这里如果不用第二个参数会怎么样呢?

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

会报一个未捕获的错,当省略第二个参数时,catch的存在就很有意义 ,关于catch我们稍后讨论
上面对第一个then的输出做了讨论,我们继续看第二then
在解释第二个then之前,我们先看另一个栗子

关于返回值

const p3 = new Promise((resolve, reject) => {
          reject('出错啦');
      })
      //第一个then
      .then(res => {
          console.log(`resolve-then1:${res}`);//“不执行这里”
          return 1;
      }, err => {
          console.log(`reject-then1:${err}`);//=>reject-then1:出错啦
          return 1
      })
//第二个then
.then(res => {
    console.log(`resolve-then2:${res}`);//=>resolve-then2:1
    throw new Error('抛出一个错')
}, err => {
    console.log(`reject-then2:${err}`);//“不执行这里”
    throw new Error('抛出一个错')
})
//第三个then
.then(res => {
    console.log(`resolve-then3:${res}`);//“不执行这里”
}, err => {
    console.log(`reject-then3:${err}`);//=>reject-then3:Error: 抛出一个错
})
//第四个then
.then(res => {
    console.log(`resolve-then4:${res}`);//=>resolve-then4:undefined
}, err => {
    console.log(`reject-then4:${err}`);//“不执行这里”
})
//第五个then
.then(res => {
    console.log(`resolve-then5:${res}`);//=>resolve-then5:undefined
}, err => {
    console.log(`reject-then5:${err}`);//“不执行这里”
})

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

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

上面罗列了我能找到的所有返回值的情况
在MDN上关于返回值的介绍,解释的有点绕
《javascript异步之Promise then和catch》
我们分析一下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

我们做一下总结

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

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

Promise.reject('出错啦')
    .then(res => console.log(`res1:${res}`), err => console.log(`err1:${err}`))
    .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的第二个参数。
我们将上面的栗子做一下改动

const p4 = new Promise((resolve, reject) => {
    reject('出错啦');
})
.catch(err => {
    console.log(`catch1:${err}`);//=>catch1:出错啦
    return 1;
})
.then(res => {
    console.log(`then1:${res}`);//=>then1:1
    throw new Error('抛出一个错')
})
.catch(err => {
    console.log(`catch2:${err}`);//=>catch2:Error: 抛出一个错
})
.then(res => {
    console.log(`then2:${res}`);//=>then2:undefined
})

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

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

END

参考链接
ES6 Promise——then与catch的返回值实践
ES6关于Promise的用法
promise的.then返回的一个新promise,他的状态和值,问题好大
Promise.prototype.then()

发表评论

电子邮件地址不会被公开。 必填项已用*标注