2015-08-16 115 views
27

有許多關於如何在使用JavaScript Promise進行編程時使用「then」和「catch」的教程。然而,所有這些教程似乎都錯過了一個重要的觀點:從一個當時/ catch塊返回來打破Promise鏈。讓我們從一些同步代碼開始來說明這個問題:如何從Promise的catch/then塊返回

try { 
    someFunction(); 
} catch (err) { 
    if (!(err instanceof MyCustomError)) 
    return -1; 
} 
someOtherFunction(); 

從本質上說,我測試捕獲錯誤,如果它不是我期望我會返回給調用者的錯誤,否則程序繼續執行。然而,這種邏輯不會與無極工作:

Promise.resolve(someFunction).then(function() { 
    console.log('someFunction should throw error'); 
    return -2; 
}).catch(function(err) { 
    if (err instanceof MyCustomError) { 
    return -1; 
    } 
}).then(someOtherFunction); 

這個邏輯用於一些我的單元測試,我想一個函數來以某種方式失敗。即使我將catch改爲一個block,我仍然無法打破一系列鏈接的Promise,因爲從then/catch塊返回的任何內容都將成爲沿着鏈傳播的Promise。

我不知道Promise能否實現這個邏輯;如果不是,爲什麼?我非常奇怪,一個Promise鏈永遠不會被打破。謝謝!

2015年8月16日編輯: 根據到目前爲止給出的答案,當時塊返回的拒絕Promise將通過Promise鏈傳播並跳過所有隨後的塊,直到被捕獲(處理)。這種行爲很好理解,因爲它簡單地模仿下面的同步碼(方法1):

try { 
    Function1(); 
    Function2(); 
    Function3(); 
    Function4(); 
} catch (err) { 
    // Assuming this err is thrown in Function1; Function2, Function3 and Function4 will not be executed 
    console.log(err); 
} 

不過,我問是在同步代碼下面的情況下(方法2):

try { 
    Function1(); 
} catch(err) { 
    console.log(err); // Function1's error 
    return -1; // return immediately 
} 
try { 
    Function2(); 
} catch(err) { 
    console.log(err); 
} 
try { 
    Function3(); 
} catch(err) { 
    console.log(err); 
} 
try { 
    Function4(); 
} catch(err) { 
    console.log(err); 
} 

我想以不同的方式處理不同功能中出現的錯誤。我可能會捕獲一個catch塊中的所有錯誤,如方法1所示。但是這樣我必須在catch塊內部做一個大的switch語句來區分不同的錯誤;此外,如果由不同函數引發的錯誤沒有共同的可切換屬性,我將根本無法使用switch語句;在這種情況下,我必須爲每個函數調用使用單獨的try/catch塊。方法2有時是唯一的選擇。 Promise是否不支持這種方法與它的catch/catch語句?

+2

爲什麼不''返回Promise.reject()'? – elclanrs

+0

從當前塊返回被拒絕的承諾將使得當時的塊返回被拒絕的承諾,該承諾通過承諾鏈傳播直到它被捕獲。 – lixiang

+0

根據您的澄清更新了我的答案。 – rrowland

回答

45

這不能用語言的特徵來實現。但是,基於模式的解決方案是可用的。

以下是兩種解決方案。

以前重新拋出錯誤

這種模式基本上是健康的...

Promise.resolve() 
.then(Function1).catch(errorHandler1) 
.then(Function2).catch(errorHandler2) 
.then(Function3).catch(errorHandler3) 
.then(Function4).catch(errorHandler4) 
.catch(finalErrorHandler); 

Promise.resolve()不是絕對必要的,但允許所有.then().catch()線是相同的圖案,和整個表情在眼睛上更容易。

...但是:

  • 如果errorHandler返回結果,那麼鏈將進行到下一行的成功處理程序。
  • 如果errorHandler拋出,那麼鏈將前進到下一行的錯誤處理程序。

除非編寫錯誤處理程序,以便它們可以區分先前拋出的錯誤和新拋出的錯誤,否則不會發生期望的跳出鏈。例如:

function errorHandler1(error) { 
    if (error instanceof MyCustomError) { // <<<<<<< test for previously thrown error 
     throw error; 
    } else { 
     // do errorHandler1 stuff then 
     // return a result or 
     // throw new MyCustomError() or 
     // throw new Error(), new RangeError() etc. or some other type of custom error. 
    } 
} 

現在:

  • 如果設置ErrorHandler返回結果,那麼鏈將前進到下一個功能N。
  • 如果errorHandler拋出一個MyCustomError,那麼它將被重複地重新拋出鏈並被不符合if(error instanceof MyCustomError)協議的第一個錯誤處理程序(例如最終的.catch())捕獲。
  • 如果errorHandler拋出任何其他類型的錯誤,那麼鏈將進行到下一個捕獲。

如果您需要根據拋出的錯誤的類型跳過鏈接結束的靈活性,此模式將非常有用。我期望的情況很少。

DEMO

絕緣漁獲

另一個解決方案是引入一種機制,以保持每個.catch(errorHandlerN)「絕緣」使得它將只捕獲來自引起的誤差對應FunctionN,而不是從任何前面的錯誤。

這可以通過在主鏈中只有成功處理程序來實現,每個成功處理程序包含一個包含子鏈的匿名函數。

Promise.resolve() 
.then(function() { return Function1().catch(errorHandler1); }) 
.then(function() { return Function2().catch(errorHandler2); }) 
.then(function() { return Function3().catch(errorHandler3); }) 
.then(function() { return Function4().catch(errorHandler4); }) 
.catch(finalErrorHandler); 

這裏Promise.resolve()起着重要的作用。沒有它,Function1().catch(errorHandler1)將在主鏈中,catch()不會與主鏈絕緣。

現在,

  • 如果設置ErrorHandler返回結果,那麼鏈將前進到下一行。
  • 如果errorHandler拋出任何喜歡的東西,那麼鏈將直接進入finalErrorHandler。

如果您希望總是跳到鏈尾,而不管拋出的錯誤類型如何,請使用此模式。自定義錯誤構造函數不是必需的,錯誤處理程序不需要以特殊方式寫入。

DEMO

使用場合

哪種模式選擇將已定的因素決定,但也可能由項目團隊的性質。

  • 一人團隊 - 你寫一切,理解問題 - 如果你可以自由選擇,然後運行你的個人喜好。
  • 多人團隊 - 一人編寫主鏈,其他人編寫函數及其錯誤處理程序 - 如果可以的話,選擇絕緣捕獲 - 一切都在主鏈的控制下,不需要強制執行以某種方式編寫錯誤處理程序的原則。
+0

謝謝你這個全面的答案!我已將你的回答標記爲答案。但是,根據我對這個問題的進一步瞭解,我認爲試圖從catch塊返回或跳過Promise鏈不應該被濫用。 Promise鏈中的「then」塊表示整個過程的一個步驟。如果我們想把它作爲引發獨特錯誤的個別陳述來對待,我們最好不要使用Promise鏈 – lixiang

+1

沒有濫用。像上面這些模式對於異步流量控制來說非常普遍且非常必要。 –

+0

很好的回答!但我認爲你的第一個小提琴在執行myCustomError時遇到了一個問題,就是它使得它的行爲與任何其他錯誤一樣(看到它只是取消註釋'reject(new Error(...')),所以我認爲第5行應該是: 'myCustomError.prototype = Error;' –

4

沒有內置的功能來跳過您請求的剩餘鏈的全部。但是,你可以通過每個抓拋出一定的誤差模仿這種行爲:

doSomething() 
    .then(func1).catch(handleError) 
    .then(func2).catch(handleError) 
    .then(func3).catch(handleError); 

function handleError(reason) { 
    if (reason instanceof criticalError) { 
    throw reason; 
    } 

    console.info(reason); 
} 

如果任何catch塊的抓住了criticalError他們會直接跳到結束,並拋出錯誤。在繼續下一個.then塊之前,任何其他錯誤都將被控制檯記錄下來。

9

首先,我看到了這部分代碼中的一個常見錯誤,它可能會讓您感到困惑。這是您的示例代碼塊:

Promise.resolve(someFunction()).then(function() { 
    console.log('someFunction should throw error'); 
    return -2; 
}).catch(function(err) { 
    if (err instanceof MyCustomError) { 
    return -1; 
    } 
}).then(someOtherFunction()); 

您需要傳遞函數引用,而不是實際調用函數並傳遞它們的返回結果。所以,這上面的代碼也許應該是這樣的:

Promise.resolve(someFunction).then(function() { 
    console.log('someFunction should throw error'); 
    return -2; 
}).catch(function(err) { 
    if (err instanceof MyCustomError) { 
    // returning a normal value here will take care of the rejection 
    // and continue subsequent processing 
    return -1; 
    } 
}).then(someOtherFunction); 

注意您的兩個功能後,我已經刪除()所以你只是路過的功能參考,不立即調用該函數。這將允許承諾基礎設施決定是否在將來調用承諾。如果你犯了這個錯誤,它會完全拋棄你的承諾,因爲事情會被調用。


關於捕獲拒絕的三個簡單規則。

  1. 如果沒有人收到拒絕,它立即停止承諾鏈,原來的拒絕成爲承諾的最終狀態。沒有後續的處理程序被調用。
  2. 如果承諾拒絕被捕獲,要麼不返回任何結果或任何正常的值是從拒絕處理器返回,則拒絕被認爲是處理,並且承諾繼續鏈和後續的處理程序被調用。無論你從拒絕處理程序返回什麼,都會成爲promise的當前值,就好像拒絕程序從未發生過(除了這個級別的解析處理程序沒有被調用 - 拒絕處理程序被調用)。
  3. 如果承諾拒絕被捕獲並您無論是從廢品處理程序拋出一個錯誤,或者返回拒絕的承諾,那麼所有的決心處理被跳過,直到下一個鏈中的廢品處理。如果沒有拒絕處理程序,那麼承諾鏈將停止,並且新鑄造的錯誤將成爲承諾的最終狀態。

你可以看到在this jsFiddle幾個例子,它顯示了三種情況:

  1. 從廢品處理程序返回常規值,將導致未來.then()決心處理程序被調用(例如:正常處理繼續),

  2. 在拒絕處理程序中投擲會導致正常的解析處理停止,並且會跳過所有解析處理程序,直到找到拒絕處理程序或鏈的末尾。如果在解析處理程序中發現意外錯誤(我認爲是您的問題),這是停止鏈的有效方法。

  3. 沒有一個拒絕處理當前導致正常解析處理,停止所有決心處理被跳過,直到你得到一個拒絕處理程序或鏈的末端。

+0

謝謝你的收穫,我改變了!你能看看我的擴展問題嗎? – lixiang

+0

@lixiang - 你不能使用'return'來停止承諾中的所有進一步處理。你不能因爲他們的異步性質,所以實際上沒有一個確切的類比。我已經概述了可用於承諾的一般選項。如果所有下游的'.catch()'處理程序檢查錯誤類型並重新拋出它,如果它不是他們特別處理的東西,那麼你可以拋出一個別人不會處理的唯一錯誤,並且它會停止所有進一步的處理。它將遍歷所有下游的'.catch()'處理程序,但每個處理程序都會重新生成以最終到達鏈的末尾。 – jfriend00

+0

@lixiang - 你甚至可以創建自己的'abort'類錯誤(Error類的一個子類)的規則,即不需要重新拋出中間流'.catch()'處理程序。但是,承諾並沒有內置這樣的東西。 – jfriend00