繁体   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