中断或取消Promise链的可行方案

ES6标准引入的异步编程解决方案Promise,能够将层层嵌套的回调转化成扁平的Promise链式调用,优雅地解决了“回调地狱”的问题。
当Promise链中抛出一个错误时,错误信息沿着链路向后传递,直至被捕获。利用这个特性能跳过链中函数的调用,直至链路终点,变相地结束Promise链。

 1 Promise.resolve()
 2     .then(() => {
 3         console.log('[onFulfilled_1]');
 4         throw 'throw on onFulfilled_1';
 5     })
 6     .then(() => {  // 中间的函数不会被调用
 7         console.log('[onFulfilled_2]');
 8     })
 9     .catch(err => {
10         console.log('[catch]', err);
11     });
12 // => [onFulfilled_1]
13 // => [catch] throw on onFulfilled_1

然而,若链路中也对错误进行了捕获,则后续的函数可能会继续执行。

 1 Promise.resolve()
 2     .then(() => {
 3         console.log('[onFulfilled_1]');
 4         throw 'throw on onFulfilled_1';
 5     })
 6     .then(() => {
 7         console.log('[onFulfilled_2]');
 8     }, err => {     // 捕获错误
 9         console.log('[onRejected_2]', err);
10     })
11     .then(() => {   // 该函数将被调用
12         console.log('[onFulfilled_3]');
13     })
14     .catch(err => {
15         console.log('[catch]', err);
16     });
17 // => [onFulfilled_1]
18 // => [onRejected_2] throw on onFulfilled_1
19 // => [onFulfilled_3]

解决方案
Promise的then方法接收两个参数:
Promise.prototype.then(onFulfilled, onRejected)
若onFulfilled或onRejected是一个函数,当函数返回一个新Promise对象时,原Promise对象的状态将跟新对象保持一致,详见Promises/A+标准。
因此,当新对象保持“pending”状态时,原Promise链将会中止执行。

 1 Promise.resolve()
 2     .then(() => {
 3         console.log('[onFulfilled_1]');
 4         return new Promise(()=>{}); // 返回“pending”状态的Promise对象
 5     })
 6     .then(() => {                   // 后续的函数不会被调用
 7         console.log('[onFulfilled_2]');
 8     })
 9     .catch(err => {
10         console.log('[catch]', err);
11     });
12 // => [onFulfilled_1]

 

主要问题是:链式调用时,想在下层返回resolve的情况下,需要在中途得到某种resolve结果就终止调用链。(PS:下层可能是调用其他人编写模块,比如参数不对,它仍会返回resolve,出现错误才会reject,本身下层Promise 返回reject是可以打断调用链的)

下面有个链式调用Promise的测试函数

 1 const promiseFun = function(param1){
 2     return new Promise((resolve, reject)=>{
 3         resolve(param1);
 4     });
 5 }
 6 const promiseTest = function(param1, param2){
 7     return new Promise((resolve, reject)=>{
 8         promiseFun(1).then((number)=>{
 9             console.info(`fun1 result:${number}`);
10             return promiseFun(2);
11         }).then((number)=>{
12             console.info(`fun2 result:${number}`);
13             return promiseFun(3);
14         }).then((number)=>{
15             console.info(`fun3 result:${number}`);
16             return promiseFun(4);
17         }).then((number)=>{
18             console.info(`fun4 result:${number}`);
19         }).catch((err)=>{
20             console.info(`promiseTest error:${err}`);
21         });
22     });        
23 }
24 promiseTest('1','2').then((number)=>{
25     console.info(`promiseTest:${number}`);
26 }).catch((err)=>{
27     console.info(`promiseTest failed:${err}`);
28 });

现在遇到的一个问题是,比如我们在fun2时,我们调用reject 想终止该链式调用,但实际的结果是仍然会跑到

1 console.info(`fun3 result:${number}`)及console.info(`fun4 result:${number}`)。

PS: 这种想法本身就很奇怪,因为我们调用reject 只是影响promiseTest 下创建那个Promise的状态。也就是调用reject后promiseTest 这个函数会触发onRejected状态。而链式Promise调用状态是由下层Promise对象的状态决定。

 1 promiseFun(1).then((number)=>{
 2             console.info(`fun1 result:${number}`);
 3             return promiseFun(2);
 4         }).then((number)=>{
 5             console.info(`fun2 result:${number}`);
 6            if(number === 2){
 7                 reject(number)
 8             }
 9             else{
10                 return promiseFun(3);
11             }
12         }).then((number)=>{
13             console.info(`fun3 result:${number}`);
14             return promiseFun(4);
15         }).then((number)=>{
16             console.info(`fun4 result:${number}`);
17         }).catch((err)=>{
18             console.info(`promiseTest error:${err}`);
19         });

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)的处理函数就都不会跑到了。即:

1 return (new Promise((resolve, reject)=>{}));//返回pending状态
2 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);
}

 

posted @ 2019-12-23 16:12  xfcao  阅读(3741)  评论(0编辑  收藏  举报