中斷或取消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);
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM