[英]How to handle error properly in Promise chain?
假設我們有3個異步任務返回Promises: A
, B
和C
我們希望將它們鏈接在一起(也就是說,為了清楚起見,使用A
返回的值並使用它調用B
) ,但也希望正確處理每個錯誤,並在第一次失敗時突破。 目前,我看到了兩種方法:
A
.then(passA)
.then(B)
.then(passB)
.then(C)
.then(passC)
.catch(failAll)
這里, passX
函數處理每次調用X
的成功。 但是在failAll
函數中,我們必須處理A
, B
和C
所有錯誤,這些錯誤可能很復雜且不易讀取,特別是如果我們有超過3個異步任務。 所以另一種方式考慮到這一點:
A
.then(passA, failA)
.then(B)
.then(passB, failB)
.then(C)
.then(passC, failC)
.catch(failAll)
在這里,我們將原始failAll
的邏輯分離為failA
, failB
和failC
,這似乎簡單易讀,因為所有錯誤都在其源旁邊處理。 但是,這不符合我的要求。
讓我們看看A
失敗(被拒絕), failA
不能繼續調用B
,因此必須拋出異常或者拒絕。 但是這兩個都被failB
和failC
,這意味着failB
和failC
需要知道我們是否已經失敗 ,可能是通過保持狀態(即變量)。
此外,似乎我們擁有的異步任務越多,我們的failAll
函數的大小(方式1)就越大,或者更多的failX
函數被調用(方式2)。 這讓我想到了我的問題:
有一個更好的方法嗎?
考慮:由於then
異常是由拒絕方法處理的,是否應該有Promise.throw
方法來實際中斷鏈?
一個可能的重復 ,答案可以在處理程序中添加更多范圍。 承諾是不是應該遵循函數的線性鏈接,而不是傳遞傳遞函數的函數的函數?
你有幾個選擇。 首先,讓我們看看我是否可以提煉您的要求。
您希望在錯誤發生的位置附近處理錯誤,因此您沒有一個錯誤處理程序,必須對所有可能的不同錯誤進行排序以查看要執行的操作。
當一個承諾失敗時,您希望能夠中止鏈的其余部分。
一種可能性是這樣的:
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
});
然后,在每個failA
, failB
, failC
,您將獲得該步驟的特定錯誤。 如果要中止鏈,則在函數返回之前重新拋出。 如果您希望鏈繼續,您只需返回正常值。
上面的代碼也可以這樣編寫(如果passB
或passC
拋出或返回被拒絕的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.