ES6标准引入的异步编程解决方案Promise,能够将层层嵌套的回调转化成扁平的Promise链式调用,优雅地解决了“回调地狱”的问题。
当Promise链中抛出一个错误时,错误信息沿着链路向后传递,直至被捕获。利用这个特性能跳过链中函数的调用,直至链路终点,变相地结束Promise链。
Promise.resolve()
.then(() => {
console.log('[onFulfilled_1]');
throw 'throw on onFulfilled_1';
})
.then(() => { // 中间的函数不会被调用
console.log('[onFulfilled_2]');
})
.catch(err => {
console.log('[catch]', err);
});
// => [onFulfilled_1]
// => [catch] throw on onFulfilled_1
然而,若链路中也对错误进行了捕获,则后续的函数可能会继续执行。
Promise.resolve()
.then(() => {
console.log('[onFulfilled_1]');
throw 'throw on onFulfilled_1';
})
.then(() => {
console.log('[onFulfilled_2]');
}, err => { // 捕获错误
console.log('[onRejected_2]', err);
})
.then(() => { // 该函数将被调用
console.log('[onFulfilled_3]');
})
.catch(err => {
console.log('[catch]', err);
});
// => [onFulfilled_1]
// => [onRejected_2] throw on onFulfilled_1
// => [onFulfilled_3]
解决方案
Promise的then方法接收两个参数:
Promise.prototype.then(onFulfilled, onRejected)
若onFulfilled或onRejected是一个函数,当函数返回一个新Promise对象时,原Promise对象的状态将跟新对象保持一致,详见Promises/A+标准。
因此,当新对象保持“pending”状态时,原Promise链将会中止执行。
Promise.resolve()
.then(() => {
console.log('[onFulfilled_1]');
return new Promise(()=>{}); // 返回“pending”状态的Promise对象
})
.then(() => { // 后续的函数不会被调用
console.log('[onFulfilled_2]');
})
.catch(err => {
console.log('[catch]', err);
});
// => [onFulfilled_1]
主要问题是:链式调用时,想在下层返回resolve的情况下,需要在中途得到某种resolve结果就终止调用链。(PS:下层可能是调用其他人编写模块,比如参数不对,它仍会返回resolve,出现错误才会reject,本身下层Promise 返回reject是可以打断调用链的)
下面有个链式调用Promise的测试函数
const promiseFun = function(param1){
return new Promise((resolve, reject)=>{
resolve(param1);
});
}
const promiseTest = function(param1, param2){
return new Promise((resolve, reject)=>{
promiseFun(1).then((number)=>{
console.info(`fun1 result:${number}`);
return promiseFun(2);
}).then((number)=>{
console.info(`fun2 result:${number}`);
return promiseFun(3);
}).then((number)=>{
console.info(`fun3 result:${number}`);
return promiseFun(4);
}).then((number)=>{
console.info(`fun4 result:${number}`);
}).catch((err)=>{
console.info(`promiseTest error:${err}`);
});
});
}
promiseTest('1','2').then((number)=>{
console.info(`promiseTest:${number}`);
}).catch((err)=>{
console.info(`promiseTest failed:${err}`);
});
现在遇到的一个问题是,比如我们在fun2时,我们调用reject 想终止该链式调用,但实际的结果是仍然会跑到
console.info(`fun3 result:${number}`)及console.info(`fun4 result:${number}`)。
PS: 这种想法本身就很奇怪,因为我们调用reject 只是影响promiseTest 下创建那个Promise的状态。也就是调用reject后promiseTest 这个函数会触发onRejected状态。而链式Promise调用状态是由下层Promise对象的状态决定。
promiseFun(1).then((number)=>{
console.info(`fun1 result:${number}`);
return promiseFun(2);
}).then((number)=>{
console.info(`fun2 result:${number}`);
if(number === 2){
reject(number)
}
else{
return promiseFun(3);
}
}).then((number)=>{
console.info(`fun3 result:${number}`);
return promiseFun(4);
}).then((number)=>{
console.info(`fun4 result:${number}`);
}).catch((err)=>{
console.info(`promiseTest error:${err}`);
});
2.2 原因
Promise的then方法接收两个参数:
Promise.prototype.then(onFulfilled, onRejected)
若onFulfilled或onRejected是一个函数,当函数返回一个新Promise对象时,原Promise对象的状态将跟新对象保持一致。
来自:https://promisesaplus.com/
解释下原因:
为什么我们链式调用中reject没有作用?
因为reject仅仅改变的是外层包的promiseTest 返回Promise状态。而链式调用的状态是由promiseFun 返回的状态决定, 而在第二个链式调用then时我们调用reject改变了promiseTest 那种Promise的状态进而使promiseTest触发onRejected状态打印 promiseTest failed:${err},而链式第二个链式调用中本身没做修改链式调用的状态,所以第三个链式继承了第一个链式调用返回的Promise的resolve状态,导致链式调用继续向下运行。
2.3 解决方案
而针对上面的问题,我们想要在resolve的情况下,中断或终止链式调用。
还是基于Promise的特点:原Promise对象的状态将跟新对象保持一致。
我们仅需要在链式调用中,返回一个pending 状态或reject状态的Promise对象即可。后面then 所有resolve(onFulfilled)的处理函数就都不会跑到了。即:
return (new Promise((resolve, reject)=>{}));//返回pending状态
return (new Promise((resolve, reject)=>{reject()}));//返回reject状态 会被最后catch捕获。
在测试代码中就想这样
then((number)=>{
console.info(`fun2 result:${number}`);
if(number === 2){、
return (new Promise((resolve, reject)=>{}));
reject(number)
}
else{
return promiseFun(3);
}