2017-03-28 56 views
11

假設我們有3個異步任務返回Promises:ABC。我們想將它們鏈接在一起(也就是說,爲了清晰起見,取A返回的值並調用B,但也想正確處理每個錯誤,並在第一次失敗時突然出現。目前,我看到2種方式這樣做的:如何在Promise鏈中正確處理錯誤?

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

這裏,passX函數處理調用X成功的每一個。但是在failAll函數中,我們必須處理所有的錯誤A,BC,這可能很複雜並且不容易閱讀,特別是如果我們有超過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),或者更多的函數被調用(方式2)。這使我想到我的問題:

有沒有更好的方法來做到這一點?

注意事項:由於then中的例外情況是由拒收方法處理的,是否應該有一個Promise.throw方法來實際中斷鏈?

A possible duplicate,其答案是在處理程序中添加更多範圍。是否承諾承諾函數的線性鏈接,並且不傳遞傳遞傳遞函數的函數的函數?

+1

[打破諾言鏈的可能的複製,並呼籲根據鏈的步驟的功能,其中它是壞了(被拒絕)](http://stackoverflow.com/questions/20714460/break-promise-chain-and-call-a-function-based-on-the-step-in-the-chain-where-it ) – wing

+0

@wing對不起,我應該澄清一下 - 在函數'passX'和'failX'中,我實際上是在處理調用'X'的成功。例如,'A'是對API的XMLHTTPRequest GET調用,'passA'在繼續調用另一個API'B'之前更新相應DOM對象的狀態以表示「獲取」。 我會在我的問題中記下這一點。 –

+0

「*不承諾承諾函數的線性鏈接*」 - 不適用於比鏈更多的錯誤處理。從[適當的方式跳過一個然後函數的承諾](http://stackoverflow.com/q/21576862/1048572):*對於分支,你將永遠需要一個額外的嵌套層次。*承諾的點只是你仍然可以從這些嵌套函數中返回,而不需要調用回調函數。 – Bergi

回答

5

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

  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拋出或返回拒絕承諾稍有不同的行爲。

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

鏈很多的承諾時,另一個有用的一點是,如果你做的每一個獨特的錯誤類別拒絕錯誤,那麼你可以在錯誤的最終.catch()處理程序使用instanceof,如果你需要知道那裏的類型更容易開關,一步造成了中止的連鎖。像Bluebird這樣的庫提供了特定的.catch()語義來製作只捕獲特定類型錯誤的.catch()(就像try/catch那樣)。你可以在這裏看到藍鳥如何做到這一點:http://bluebirdjs.com/docs/api/catch.html。如果你打算處理每個錯誤(如上面的例子),那麼這是不需要的,除非你仍然需要在最後的.catch()步驟知道哪一步導致錯誤。

3

有兩種方法,我建議(這取決於你想用這個來完成的):

是的,你要處理的承諾鏈中的所有錯誤,用單一漁獲物。

如果你需要知道哪一個失敗了,你可以拒絕一個獨特的信息或值,這樣的承諾:

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

您可以branch the promise-chain,但說實話,早期的錯誤處理並不是正確的方法,尤其是對於像可讀性這樣的毛病來說。對於同步代碼也是如此,即每個單一功能都不需要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))) 
+0

但是''fail''會在'A'失敗時用'null'調用。我認爲這正是他不想要的。 – Bergi

+0

@Bergi你是對的。我改變了我的答案。謝謝! – jib

相關問題