《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对象的状态发生改变时才会执行回调!注意是执行回调不是指定回调,指定回调是可以在状态发生改变之前的。

浙公网安备 33010602011771号