簡體   English   中英

如何在Promise鏈中正確處理錯誤?

[英]How to handle error properly in Promise chain?

假設我們有3個異步任務返回Promises: ABC 我們希望將它們鏈接在一起(也就是說,為了清楚起見,使用A返回的值並使用它調用B ,但也希望正確處理每個錯誤,並在第一次失敗時突破。 目前,我看到了兩種方法:

A
.then(passA)
.then(B)
.then(passB)
.then(C)
.then(passC)
.catch(failAll)

這里, passX函數處理每次調用X的成功。 但是在failAll函數中,我們必須處理ABC 所有錯誤,這些錯誤可能很復雜且不易讀取,特別是如果我們有超過3個異步任務。 所以另一種方式考慮到這一點:

A
.then(passA, failA)
.then(B)
.then(passB, failB)
.then(C)
.then(passC, failC)
.catch(failAll)

在這里,我們將原始failAll的邏輯分離為failAfailBfailC ,這似乎簡單易讀,因為所有錯誤都在其源旁邊處理。 但是,這不符合我的要求。

讓我們看看A失敗(被拒絕), failA不能繼續調用B ,因此必須拋出異常或者拒絕。 但是這兩個都被failBfailC ,這意味着failBfailC需要知道我們是否已經失敗 ,可能是通過保持狀態(即變量)。

此外,似乎我們擁有的異步任務越多,我們的failAll函數的大小(方式1)就越大,或者更多的failX函數被調用(方式2)。 這讓我想到了我的問題:

有一個更好的方法嗎?

考慮:由於then異常是由拒絕方法處理的,是否應該有Promise.throw方法來實際中斷鏈?

一個可能的重復 ,答案可以在處理程序中添加更多范圍。 承諾是不是應該遵循函數的線性鏈接,而不是傳遞傳遞函數的函數的函數?

你有幾個選擇。 首先,讓我們看看我是否可以提煉您的要求。

  1. 您希望在錯誤發生的位置附近處理錯誤,因此您沒有一個錯誤處理程序,必須對所有可能的不同錯誤進行排序以查看要執行的操作。

  2. 當一個承諾失敗時,您希望能夠中止鏈的其余部分。

一種可能性是這樣的:

A().then(passA).catch(failA).then(val => {
    return B(val).then(passB).catch(failB);
}).then(val => {
    return C(val).then(passC).catch(failC);
}).then(finalVal => {
    // chain done successfully here
}).catch(err => {
    // some error aborted the chain, may or may not need handling here
    // as error may have already been handled by earlier catch
});

然后,在每個failAfailBfailC ,您將獲得該步驟的特定錯誤。 如果要中止鏈,則在函數返回之前重新拋出。 如果您希望鏈繼續,您只需返回正常值。


上面的代碼也可以這樣編寫(如果passBpassC拋出或返回被拒絕的promise,則行為略有不同。

A().then(passA, failA).then(val => {
    return B(val).then(passB, failB);
}).then(val => {
    return C(val).then(passC, failC);
}).then(finalVal => {
    // chain done successfully here
}).catch(err => {
    // some error aborted the chain, may or may not need handling here
    // as error may have already been handled by earlier catch
});

由於這些都是完全重復的,因此您可以使整個事物在任何長度的序列中都是表驅動的。

function runSequence(data) {
    return data.reduce((p, item) => {
        return p.then(item[0]).then(item[1]).catch(item[2]);
    }, Promise.resolve());
}

let fns = [
    [A, passA, failA],
    [B, passB, failB],
    [C, passC, failC]
];

runSequence(fns).then(finalVal => {
    // whole sequence finished
}).catch(err => {
    // sequence aborted with an error
});

鏈接大量承諾的另一個有用點是,如果您為每個拒絕錯誤創建一個唯一的Error類,那么如果您需要知道哪個步驟,您可以使用最終.catch()處理程序中的instanceof更輕松地切換錯誤類型導致鏈條流產。 像Bluebird這樣的庫提供了特定的.catch()語義,用於創建一個僅捕獲特定類型錯誤的.catch() (就像try / catch一樣)。 您可以在此處查看Bluebird如何做到這一點: http//bluebirdjs.com/docs/api/catch.html 如果您要根據自己的承諾拒絕處理每個錯誤(如上例所示),那么除非您仍需要知道最后的.catch()步驟導致錯誤,否則不需.catch()

我推薦兩種方式(取決於你想要用這個來完成的):

是的,您希望使用單個catch來處理promise鏈中的所有錯誤。

如果您需要知道哪一個失敗,您可以使用如下的唯一消息或值拒絕承諾:

A
.then(a => {
  if(!pass) return Promise.reject('A failed');
  ...
})
.then(b => {
  if(!pass) return Promise.reject('B failed');
  ...
})
.catch(err => {
  // handle the error
});

或者,您可以返回.then內的其他承諾

A
.then(a => {
  return B; // B is a different promise
})
.then(b => {
  return C; // C is another promise
})
.then(c => {
  // all promises were resolved
  console.log("Success!") 
})
.catch(err => {
  // handle the error
  handleError(err)
});

在每個承諾中,您將需要某種獨特的錯誤消息,以便您知道哪一個失敗。

由於這些是箭頭功能,我們可以刪除括號! 我喜歡的另一個原因是承諾

A
.then(a => B)
.then(b => C)
.then(c => console.log("Success!"))
.catch(err => handleError(err));

您可以對promise鏈進行分支 ,但老實說,早期的錯誤處理並不是正確的方法,特別是出於可讀性這樣的原因。 對於同步代碼也是如此,即不要try / catch每個函數,或者可讀性只是進入垃圾桶。

始終傳遞錯誤並在正代碼流恢復的位置“處理”它們。

如果你需要知道事情有多遠,我使用的一個技巧是一個簡單的進展計數器:

let progress = "";
A()
.then(a => (progress = "A passed", passA(a)))
.then(B)
.then(b => (progress = "B passed", passB(b)))
.then(C)
.then(c => (progress = "C passed", passC(c)))
.catch(err => (console.log(progress), failAll(err)))

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM