[英]Skipping promise chain after handling error
使用https://github.com/kriskowal/q庫,我想知道是否可以做這樣的事情:
// Module A
function moduleA_exportedFunction() {
return promiseReturningService().then(function(serviceResults) {
if (serviceResults.areGood) {
// We can continue with the rest of the promise chain
}
else {
performVerySpecificErrorHandling();
// We want to skip the rest of the promise chain
}
});
}
// Module B
moduleA_exportedFunction()
.then(moduleB_function)
.then(moduleB_anotherFunction)
.fail(function(reason) {
// Handle the reason in a general way which is ok for module B functions
})
.done()
;
基本上,如果服務結果不佳,我想使用特定於模塊A內部的邏輯來處理模塊A中的故障,但仍然跳過承諾鏈中其余的模塊B功能。
跳過模塊B功能的顯而易見的解決方案是從模塊A引發錯誤/原因。但是,我將需要在模塊B中處理該錯誤/原因。理想情況下,我希望在模塊B中不需要任何額外的代碼來完成此操作那。
這很可能是不可能的:)或違反Q的某些設計原則。
在這種情況下,您會建議什么樣的替代方案?
我有兩種方法,但都有缺點:
從模塊A拋出特定錯誤,並向模塊B添加特定處理代碼:
.fail(function(reason) { if (reason is specificError) { performVerySpecificErrorHandling(); } else { // Handle the reason in a general way which is ok for module B functions } })
在模塊A中執行自定義錯誤處理,然后在處理錯誤之后,拋出偽造的拒絕原因。 在模塊B中,添加條件以忽略虛假原因:
.fail(function(reason) { if (reason is fakeReason) { // Skip handling } else { // Handle the reason in a general way which is ok for module B functions } })
解決方案1需要向模塊B添加模塊A特定代碼。
解決方案2解決了這個問題,但是整個偽造拒絕方法似乎非常棘手。
您能推薦其他解決方案嗎?
在JavaScript中,調用函數時,代碼以兩種方式流動。
return
一個值,指示它已成功完成。 throw
錯誤,表明發生了異常操作。 看起來像:
function doSomething(){ // every function ever
if(somethingBad) throw new Error("Error operating");
return value; // successful completion.
}
try{
doSomething();
console.log("Success");
} catch (e){
console.log("Boo");
}
諾言為這種完全相同的行為建模。
在Promises中,在.then
處理程序中調用函數時,代碼以正好兩種方式流動:
return
一個承諾或一個值,表明它已成功完成。 throw
錯誤,表明發生了異常狀態。 看起來像:
var doSomething = Promise.method(function(){
if(somethingBad) throw new Error("Error operating");
return someEventualValue(); // a direct value works here too
}); // See note, in Q you'd return Q.reject()
Promise.try(function(){ // in Q that's Q().then
doSomething();
console.log("Success");
}).catch(function(e){
console.log("Boo");
});
一個promise是對概念排序操作本身的抽象。 它描述了控制如何從一個語句傳遞到另一個語句。 你可以考慮.then
在一個分號的抽象。
讓我們看看您的情況下同步代碼的外觀。
function moduleA_exportedFunction() {
var serviceResults = someSynchronousFunction();
if (serviceResults.areGood) {
// We can continue with the rest of our code
}
else {
performVerySpecificErrorHandling();
// We want to skip the rest of the chain
}
}
因此,如何繼續執行我們其余的代碼只是在returning
。 這在同步代碼和帶有承諾的異步代碼中是相同的。 執行非常具體的錯誤處理也是可以的。
我們如何跳過同步版本中的其余代碼?
doA();
doB();
doC(); // make doD never execute and not throw an exception
doD();
好吧,即使不是立即執行,也可以通過使doC進入無限循環來使doD永遠不執行:
function doC() {
if (!results.areGood) {
while(true){} // an infinite loop is the synchronous analogy of not continuing
// a promise chain.
}
}
因此, 有可能永遠都無法解決承諾-就像其他答案所建議的-返回待處理的承諾。 但是,這是非常差的流量控制,因為意圖沒有很好地傳達給用戶,並且很可能很難調試。 想象一下以下API:
moduleA_exportedFunction-此函數發出API請求,並在數據可用時作為
ServiceData
對象返回服務。 否則,它將使程序進入無限循環 。
有點混亂,不是:)嗎? 但是,它實際上存在於某些地方。 在真正古老的API中找到以下內容並不少見。
some_bad_c_api() -此函數欺騙酒吧,一旦失敗,它將終止進程 。
那么,到底要終止該API中的流程,是什么困擾我們呢?
就你而言。 ModelA只是在違反其職責范圍,它無權對程序的流程做出此類決定。 食用它的人應該做出這些決定。
更好的解決方案是拋出錯誤並讓消費者處理錯誤。 我將使用藍鳥承諾在這里,因為他們不僅是兩個數量級速度更快,並有一個更現代的API -他們也有很多更好的調試工具-在這種情況下-糖條件漁獲物和更好的堆棧跟蹤:
moduleA_exportedFunction().then(function(result){
// this will only be reached if no error occured
return someOtherApiCall();
}).then(function(result2){
// this will be called if the above function returned a value that is not a
// rejected promise, you can keep processing here
}).catch(ApiError,function(e){
// an error that is instanceof ApiError will reach here, you can handler all API
// errors from the above `then`s in here. Subclass errors
}).catch(NetworkError,function(e){
// here, let's handle network errors and not `ApiError`s, since we want to handle
// those differently
}).then(function(){
// here we recovered, code that went into an ApiError or NetworkError (assuming
// those catch handlers did not throw) will reach this point.
// Other errors will _still_ not run, we recovered successfully
}).then(function(){
throw new Error(); // unless we explicitly add a `.catch` with no type or with
// an `Error` type, no code in this chain will run anyway.
});
因此,在一行中,您將執行同步代碼中的操作,這與promise一樣。
注意Promise.method只是Bluebird用於包裝函數的便捷功能,我只是討厭同步返回Promise返回的API,因為它會造成重大破壞。
這是一種設計的東西。 通常,當模塊或服務返回承諾時,您希望它解析調用是否成功,否則失敗。 即使您知道呼叫不成功,但諾言既沒有解決也沒有失敗,基本上是一種無聲的失敗。
但是,我不知道您模塊的具體細節或原因,因此,如果您確實想在這種情況下靜默失敗,則可以通過返回未解決的承諾來做到這一點:
//模塊A
function moduleA_exportedFunction() {
return promiseReturningService().then(function(serviceResults) {
if (serviceResults.areGood) {
// We can continue with the rest of the promise chain
}
else {
performVerySpecificErrorHandling();
// We want to skip the rest of the promise chain
return q.defer().promise;
}
});
}
受本傑明·格魯恩鮑姆(Benjamin Gruenbaum)的評論和答案的啟發-如果我以同步代碼編寫此代碼,則將使moduleA_exportedFunction
返回一個shouldContinue
布爾值。
因此,有了promise,基本上就是這樣(免責聲明:這是偽代碼式的,未經測試)
// Module A
function moduleA_exportedFunction() {
return promiseReturningService().then(function(serviceResults) {
if (serviceResults.areGood) {
// We can continue with the rest of the promise chain
return true;
}
else {
performVerySpecificErrorHandling();
// We want to skip the rest of the promise chain
return false;
}
});
}
// Module B
moduleA_exportedFunction()
.then(function(shouldContinue) {
if (shouldContinue) {
return moduleB_promiseReturningFunction().then(moduleB_anotherFunction);
}
})
.fail(function(reason) {
// Handle the reason in a general way which is ok for module B functions
// (And anything unhandled from module A would still get caught here)
})
.done()
;
它確實需要模塊B中的一些處理代碼,但是邏輯既不特定於模塊A的內部,也不涉及拋出和忽略虛假錯誤-已完成任務! :)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.