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); }