假设我们有3个异步任务返回Promise: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 possible duplicate,答案可在处理程序中添加更多范围。承诺是否应该遵循函数的线性链接,而不传递传递函数的函数的函数?
答案 0 :(得分:9)
你有几个选择。首先,让我们看看我是否可以提炼您的要求。
您希望在错误发生的位置附近处理错误,因此您没有一个错误处理程序,必须对所有可能的不同错误进行排序,以查看要执行的操作。
当一个承诺失败时,您希望能够中止链的其余部分。
一种可能性是这样的:
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
抛出或返回被拒绝的承诺,行为会略有不同。
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类,那么您可以使用最终instanceof
处理程序中的.catch()
更轻松地切换错误类型如果你需要知道哪一步导致了中止链。像Bluebird这样的库提供了特定的.catch()
语义,用于创建仅捕获特定类型错误的.catch()
(就像try / catch一样)。您可以在此处查看Bluebird如何做到这一点:http://bluebirdjs.com/docs/api/catch.html。如果您要根据自己的承诺拒绝处理每个错误(如上例所示),那么除非您仍然需要在最后.catch()
步骤知道导致错误的步骤,否则不需要这样做。< / p>
答案 1 :(得分:5)
我推荐两种方式(取决于你想要用这个方法完成的事情):
是的,您希望使用单个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));
答案 2 :(得分:2)
你可以branch the promise-chain,但老实说,早期错误处理不是正确的方法,特别是出于可读性这样的愚蠢原因。对于同步代码也是如此,即不要try
/ catch
每个函数,或者可读性只是垃圾。
总是传递错误并且&#34;处理&#34;他们在正面代码流程恢复的时候。
如果你需要知道事情的进展,我使用的一个技巧是一个简单的进展计数器:
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)))