《Promise学习笔记》-3Promise的几个关键问题

1 如何改变Promise状态

在关于Promise的第一篇博客中提到过,一个Promise实例对象只有三种状态:pending、fulfilled、rejected。并且状态的改变只发生一次,要么从pending变为成功状态fulfilled,要么从pending变为失败状态rejected。不存在先从pending变为成功状态,再变成失败状态的情况。

那么如何才能改变状态呢?之前博客中也提到过:可以调用resolve函数将Promise状态变为成功,也可以调用reject函数将Promise状态变为失败。实际上还有另外一种方法,那就是抛出异常,此时会将Promise状态变为失败!

  • 调用resolve函数
const p = new Promise((resolve,reject)=>{
    resolve(111);
})
console.log(p)        // 状态为成功,状态值为111
  • 调用reject函数
const p = new Promise((resolve,reject)=>{
    reject(222);
})
console.log(p)       // 状态为失败,状态值为222
  • 抛出异常
const p = new Promise((resolve,reject)=>{
    throw '333';
})
console.log(p)       // 状态为失败,状态值为333

2 能否执行多个回调

通俗地说就是如果为一个Promise指定多个回调,那么这些回调都会被调用吗?首先需要明确的Promise通过then方法指定回调,then方法通常接收两个函数形式的参数,第一个作为成功状态的回调,第二个作为失败状态的回调。需要注意的是Promise只有在状态发生改变后才会执行回调函数,当一个Promise为pending状态时并不会执行回调的。可以验证一下:

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

});
p.then(value => {
    console.log(111);
},reason => {
    console.log(222);
})

上面提到Promise状态改变可以通过调用resolve函数或者reject函数,或者直接抛出异常。所以在上面的例子中,实例对象p永远是pending状态,可以发现控制台没有任何输出,也就是说虽然指定了回调函数,但是并没有执行。总结一下就是:回调函数只有在Promise对象状态发生改变之后才会被执行?

然而这里的问题是如果指定了多个回调,是否都会被执行呢?答案是只要状态发生改变,那么所指定的对应的回调都会被执行!可以验证一下:

const p = new Promise((resolve,reject)=>{
    resolve()
});
// 成功状态的回调1
p.then(value => {
    console.log(111);
})
// 成功状态的回调2
p.then(value => {
    console.log(222);
})

以上代码输出结果为111和222,这就说明所指定的成功的回调都被执行了!这里要注意一点就是:上述代码是将Promise实例对象变为成功状态,那么肯定会都执行对应的成功状态的回调,如果这时候也给了失败的回调,肯定是不执行的。

3 改变Promise状态和指定回调函数的先后问题

上面这个问题用代码层面理解就是:是resolve或者其他方法改变状态先执行还是then方法指定回调先执行?注意,这里是指定回调并不意味着执行回调函数,千万不要混淆!!

正常情况下是先指定回调在改变状态,但是也可以先改变状态再执行回调。如果构造器函数中是同步任务,那么会先改变状态在指定回调;但是如果是异步任务,比如定时器、发送AJAX请求等,则会先指定回调再改变状态。

那么与此同时,所指定的回调函数什么时候执行呢?正如前面提到过的,只有在Promise实例对象状态发送改变之后才会执行相应的回调函数!所以如果先指定回调再改变状态,那么在改变状态后就会执行相应的回调函数;如果先改变状态在指定回调,那么在指定回调后就会立即执行相应的回调。总结一下:只有在状态发生改变之后才会执行回调函数,得到相应的数据!

4 Promise如何串联多个操作任务

其实换个问法应该更常见,就是Promise为什么可以实现链式调用?我们都知道,一个Promise实例对象的回调函数的返回结果依旧是一个Promise对象,那么就可以给这个回调继续指定一个回调,从而串联多个操作任务,形成链式调用。

const p = new Promise((resolve,reject)=>{
    resolve('OK');
});
p.then(value => {
    console.log(value);      // OK
}).then(value => {
    console.log(value);      // undefined
})

以上代码都能得到输出,这就说明Promise能够实现链式调用。这里需要注意几点:第一、通过调用resolve函数将Promise实例对象p的状态修改为成功,并且成功的值为OK,所以会调用then方法中第一个对应成功的回调,输出OK;第二、此时第一个回调所返回的Promise是成功状态的,成功的状态值为undefined,因为此时没有return任何值,所以默认返回了undefined;第三、由于第一个回调所返回的Promise对象为成功状态,所以会继续执行第二个回调所对应的成功的回调,输出成功的状态值undefined,此时还可以给第二个回调继续指定回调!

上面例子中执行器函数和回调函数都只是包含同步任务,相应地,Promise的链式调用同样支持异步操作的场景。

const p = new Promise((resolve,reject)=>{
    setInterval(()=>{
        resolve('OK')
    },1000)
});
p.then(value => {
    return new Promise((resolve,reject)=>{
        resolve('Success');
    })
}).then(value => {
    console.log(value);         // 输出Success
})

此时通过异步任务修改状态为成功,那么当实例对象p状态发生改变后执行对应的成功的回调函数,而第一个then回调函数里返回的是一个Promise类型的对象,且调用了resolve函数修改状态,所以第一个then返回的Promise对象状态为成功,状态值为Success,在第二次执行回调时,value保存的是第一次回调的成功的状态值,所以最终结果输出Success。

5 Promise异常穿透

异常穿透是指在使用Promise的链式调用时,可以在链的最后指定一个失败的回调,一旦前面的操作出现了异常,就会直接执行最后的失败的回调!需要注意的是,异常穿透的前提是前面所有的then中都没有指定失败的回调,这样才能保证在最后的catch中执行失败的回调!!

const p = new Promise((resolve,reject)=>{
    reject('ERROR')
});
p.then(value => {
    console.log(111);
}).then(value => {
    console.log(222);
}).then(value => {
    console.log(333);
}).catch(reason => {
    console.log(444);
})

上述代码中,调用了reject函数修改p的状态为失败,并且失败的状态值为ERROR。我们知道Promise是能够实现链式调用的,但是在上面只是给每个then传入了成功的回调,且给倒数第二个then指定了失败的回调。但是可以发现代码是可以正常运行的,并且输出值为444,也就是说前面所指定的成功的回调都没有被执行!这就是Promise的异常穿透,我们只需要在链式调用的最后指定一个失败的回调,相当于一个“兜底”的作用。

6 中断Promise链

当Promise的链式调用过程中,正常情况下这个回调会被链式执行下去知道结束,那么如何才能提前中断这个链式调用呢?最好的方法就是在想要中断的地方返沪一个pending状态的Promise,因为我们知道只有当状态发生改变时才会执行回调!

const p = new Promise((resolve,reject)=>{
    resolve('OK');
});
p.then(value => {
    console.log(value);
    return new Promise(()=>{})      // 返回一个pending状态的Promise
}).then(value => {
    console.log(value);
}).then(value => {
    console.log(value);
}).then(value => {
    console.log(value);
})

上述代码中,如果没有返回pending状态的Promise,那么所有指定的回调都被会执行,而一旦添加了返回值,此时只会执行第一个回调,输出成功的状态值OK。

总结

最最重要的一点就是只有当Promise对象的状态发生改变时才会执行回调!注意是执行回调不是指定回调,指定回调是可以在状态发生改变之前的。

posted @ 2021-06-03 10:11  仙贝wang  阅读(126)  评论(0)    收藏  举报