前面我们说了,then方法会返回一个新的Promise实例对象,就说明,可以继续调用then方法,这就形成了一个链式调用。
Promise的链式调用
链式调用是很好的处理异步操作的方式,像以前没有Promise去处理多个异步任务的时候经常会遇到回调地狱的情况,后面会写一大串的代码,可读性很差而且也不方便使用,而Promise的链式调用就很好的处理了这个问题。
let p = new Promise((resolve, reject) => {
resolve("123");
});
p.then((res) => {
console.log(res);
}).then((res) => {
console.log(res);
});
要想用好链式调用首先得要知道then方法和catch方法的返回结果是怎样的,我们只有知道了新的Promise实例对象的状态和结果才能够进行后续的调用,不过这个我们在上篇文章里已经说了,现在来说一些别的情况。
第一个情况是:没有相关的后续处理,后边的任务和前面的任务保持一致。
let p = new Promise((resolve, reject) => {
resolve("123");
});
let newP = p.then((res) => {});
console.log(newP);
看这段代码,我们知道newP的状态为fulfilled,结果为undefined,这是有后续处理的情况,这里所说的后续处理是什么呢?就是这个p.then()方法的第一个回调函数,因为p的状态为fulfilled,所以调用的是成功的回调函数,所以p.then()方法的第一个回调函数就是p的后续处理。
但是如果没有后续处理呢?我们把代码修改一下:
let p = new Promise((resolve, reject) => {
resolve("123");
});
let newP = p.then(null, (res) => {});
console.log(newP);
这里p.then()方法只有失败的回调函数,没有成功的回调函数,所以p就没有后续处理了,这个时候p.then()方法返回的实例对象newP会跟p是一样的。这就是没有相关的后续处理,后边的任务和前面的任务保持一致。
换一种情况也是如此:这里p的状态是失败的,所以要调用失败的回调函数,但是没有对应的后续处理,所以就newP就和p保持一致。
let p = new Promise((resolve, reject) => {
reject("123");
});
let newP = p.then((res) => {});
console.log(newP);
第二种情况是:有相关处理,但是还没有执行,新任务就挂起。
有相关处理,但是还没有执行这句话什么意思呢?就是p有后续处理,但是p还没有执行结束,也就是状态还未改变,此时不会调用对应的回调函数,所以叫做还没有执行。
let p = new Promise((resolve, reject) => {
setTimeout(() => {
reject("123");
}, 3000);
});
let newP = p.then(null, (res) => {});
console.log(newP);
比如上面这段代码,p要在3秒以后才会改变状态,但是根据JS执行顺序,下面的let newP = p.then(null, (res) => {})会先执行(有人可能会问,不是改变状态以后才会执行then方法吗?不是这么理解的,是改变状态以后才会调用对应的回调函数,then方法是按照JS执行顺序执行的,这一点后面手写Promise的时候会详细讲到。),然后打印log,此时的newP的状态就是pending,结果为undefined,这就是所说的新任务挂起。
等3秒以后p的状态改变了,进行了后续处理,newP的状态和结果才会改变,根据上篇文章说的then方法的返回,这里没进行抛出错误也没有return,可以看作return undefined,所以newP改变后的状态应该为fulfilled,结果为undefined。当然如果没有后续处理那就是第一种情况了。
第三种情况:是有后续处理,并且已经执行,也就是上篇文章说的then方法的返回结果有哪几种情况
不明白的看上篇文章去。
Promise的链式调用在实际项目中的使用
我们在实际项目使用Promise的时候一般都会这样:
let p = new Promise((resolve, reject) => {
// 执行操作
});
p.then((res) => {}).catch((err) => {});
这也是链式调用,用then去处理成功的回调函数,用catch去处理失败的回调函数。为什么能够这么写是因为如果p状态是成功的就会直接调用then方法的第一个回调函数,如果是失败的,then方法没有第二个回调函数去处理,就出现了第一种情况,然后then方法就会返回跟p一样的实例对象,然后再去调用catch方法。
如果有多个连续的任务需要处理,那么就会去这么写:
let p = new Promise((resolve, reject) => {
// 执行操作
});
p.then((res) => {})
.then((res) => {})
.then((res) => {})
.catch((err) => {});
如果p状态是成功的就会按照顺序进行处理,一步一步执行then方法,如果p状态是失败的或者执行then方法的过程中失败了,就会根据第一种情况去调用catch方法,这种情况也叫做异常穿透。
如何中断Promise链
通过上文我们知道,链式调用中then方法或者catch方法返回的几种情况,如果是return出去一个非Promise实例对象,也会转换成一个成功状态的Promise实例对象,这个时候又会继续调用后续的处理,如果是return出去Promise实例对象,会根据Promise实例对象的状态去调用后续的处理,那么怎么才能中断这个Promise链呢?
其实在讲第二种情况的时候已经知道个大概了,就是让Promise实例对象的状态处于pending,这样就会去调用后续处理,也就是中断Promise链了。
而让前面的Promise实例对象处于pending,就只有让前面的处理去return new Promise(() => {})。
let p = new Promise((resolve, reject) => {
resolve();
});
p.then((value) => {
return "success";
})
.then((value) => {
console.log(value); // success
return new Promise(() => {});
})
.then((value) => {
console.log(value); // 不执行
})
.catch((reason) => {
console.warn(reason);
});
p为成功状态的实例对象,所以调用第一个then方法的回调函数,然后返回的也是一个成功状态的Promise实例对象,然后再去调用第二个then方法,此时返回的一个pending状态的实例对象,这时没有改变状态也就不会调用后续的处理,也就是中断Promise链。