Javascript承诺模式-区分错误
考虑这段代码:Javascript承诺模式-区分错误,javascript,node.js,promise,Javascript,Node.js,Promise,考虑这段代码: getUser(userId) .catch(function(error){ crashreporter.reportError('User DB failed', error); // show user a generic error }) .then(function(user) { return chargeCreditCard(user); }) .catch(functi
getUser(userId)
.catch(function(error){
crashreporter.reportError('User DB failed', error);
// show user a generic error
})
.then(function(user) {
return chargeCreditCard(user);
})
.catch(function(error){
crashreporter.reportError('Credit card failed', error);
// show user an error saying that their credit card got rejected
})
显然,问题在于,如果用户DB失败,那么就会执行THEN(USER)块。另一个选项是将第一个捕捉块移动到链的末端。然而,这会引起另一个问题!我们无法区分错误是来自用户数据库还是信用卡
我认为解决问题的以下模式是否被视为承诺反模式?有更好的方法吗?我看到的问题是,你最终可能会陷入半地狱
getUser(userId)
.then(function(user) {
return chargeCreditCard(user)
.catch(function(error){
crashreporter.reportError('Credit card failed', error);
// show user an error saying that their credit card got rejected
});
})
.catch(function(error){
crashreporter.reportError('User DB failed', error);
// show user a generic error
})
编辑:我想我不是很清楚。如果有更多的块,比如下面的。问题是,一旦您遇到一个错误,您就根本不希望链继续
getUser(userId)
.then(function(user) {
return chargeCreditCard(user);
}, function(error){
crashreporter.reportError('User DB failed', error);
// show user a error 1
})
.then(function(chargeId) {
return saveChargeId(chargeId);
}, function(error){
crashreporter.reportError('ChargeId DB failed', error);
// show user a error 2
})
.then(function(chargeHistoryId) {
return associateChargeToUsers(chargeHistoryId);
}, function(error){
crashreporter.reportError('chargeHistoryId DB failed', error);
// show user a error 3
})
.catch(function(error){
crashreporter.reportError('Credit card failed', error);
// show user a error 4
})
下面是如何在不嵌套回调的情况下完成“取消”链。这就是为什么承诺是解决“回调地狱”的方法
从本质上讲,如果第一个承诺失败,您将在失败回调链中传递其错误,并在它到达末尾时记录它。没有创建其他承诺,因此您在第一次失败时有效地“取消”了操作。您的链应该只有一个
catch
,但您可以为每个函数中抛出的错误添加更多上下文。例如,当chargeCreditCard
中出现错误时,您可以定位到与要报告的内容相对应的错误amessage
属性。然后在catch
错误处理程序中,您可以将消息
属性传递给报告程序:
getUser(userId)
.then(chargeCreditCard)
.catch(reportError);
function reportError(error) {
crashreporter.reportError(error.message, error);
}
我认为解决问题的以下模式是否被视为承诺反模式
不,很好
有更好的方法吗
是的,看一看。如果要严格区分成功案例(continue)和错误案例(报告此特定问题),最好将两个回调传递给,然后。这样,外部处理程序就不能由成功案例代码中的失败触发,而只能由它所安装的promise中的失败触发。就你而言:
getUser(userId)
.then(function(user) {
return chargeCreditCard(user)
.then(function(chargeId) {
return saveChargeId(chargeId)
.then(function(chargeHistoryId) {
return associateChargeToUsers(chargeHistoryId);
.then(function(result) {
return finalFormatting(result);
}, function(error){
crashreporter.reportError('chargeHistoryId DB failed', error);
return 3;
});
}, function(error){
crashreporter.reportError('ChargeId DB failed', error);
return 2;
});
}, function(error){
crashreporter.reportError('Credit card failed', error);
return 4;
});
}, function(error){
crashreporter.reportError('User DB failed', error);
return 1;
})
.then(showToUser);
尽管您可能希望使用通用错误处理程序:
getUser(userId)
.catch(function(error){
crashreporter.reportError('User DB failed', error);
throw new Error(1);
})
.then(function(user) {
return chargeCreditCard(user)
.catch(function(error){
crashreporter.reportError('Credit card failed', error);
throw new Error(4);
});
})
.then(function(chargeId) {
return saveChargeId(chargeId);
.catch(function(error){
crashreporter.reportError('ChargeId DB failed', error);
throw new Error(2);
});
})
.then(function(chargeHistoryId) {
return associateChargeToUsers(chargeHistoryId);
.catch(function(error){
crashreporter.reportError('chargeHistoryId DB failed', error);
throw new Error(3);
});
})
.then(function(result) {
return finalFormatting(result);
}, function(error) {
return error.message;
})
.then(showToUser);
在这里,每个then
回调都会返回一个承诺,该承诺会以其自身的适当错误拒绝。理想情况下,每个被调用的函数都已经这样做了,如果它们没有这样做,并且您需要向每个函数附加一个特定的catch
,那么您可能希望使用一个包装器帮助器函数(可能是crashreporter
?)的一部分)
我看到的问题是,你最终可能会陷入半地狱
getUser(userId)
.then(function(user) {
return chargeCreditCard(user)
.catch(function(error){
crashreporter.reportError('Credit card failed', error);
// show user an error saying that their credit card got rejected
});
})
.catch(function(error){
crashreporter.reportError('User DB failed', error);
// show user a generic error
})
不,这只是一个适当的包装水平。与callbackhell不同,它可以被压平,最多嵌套两个,并且它总是有一个返回值
如果您绝对希望避免嵌套和回调,请使用async
/wait
,尽管这实际上更难看:
try {
var user = await getUser(userId);
} catch(error) {
crashreporter.reportError('User DB failed', error);
return showToUser(1);
}
try {
var chargeId = chargeCreditCard(user);
} catch(error) {
crashreporter.reportError('Credit card failed', error);
return showToUser(4);
}
try {
var chargeHistoryId = saveChargeId(chargeId);
} catch(error) {
crashreporter.reportError('ChargeId DB failed', error);
return showToUser(2);
}
try {
var result = associateChargeToUsers(chargeHistoryId);
} catch(error) {
crashreporter.reportError('chargeHistoryId DB failed', error);
return showToUser(3);
}
return showToUser(finalFormatting(result));
是的,我明白。我想我的问题不是很清楚。我更新了我的问题,这就是为什么我认为这种模式容易出错和丑陋:当代码> GETUSER()/代码>失败时,您的代码不起作用,而不是抛出一个<代码>用户DB失败的错误,因为它应该抛出一个<代码> 'CalGeEythyIdB失败'错误。如果你想要一个扁平的链子,你应该像我回答的第二段那样做。我错过了那句话。我只是在头顶上打了下来。现在检查一下。它工作得很好。丑陋是你个人的喜好。承诺是为了解决地狱。你的回答充满了地狱。如果您想要一个漂亮的解决方案,可以使用async/Wait或Generators是的,但是很容易错过,而且会出现大量重复代码,不得不一遍又一遍地重复错误消息。我的回答可能充满了回电,但不是地狱般的。我相信第三个代码片段在各个方面都优于所有其他解决方案。甚至async/await也没有那么漂亮或简洁。显然,可以对其进行重构以避免错误消息的重复。箭头函数也会有所帮助。对我来说,单一深度层次的简单性仍然是可取的。其他一切都只是标签与空间你可能还想看一看,相关:我不同意这是好的。如果说承诺有什么反模式的话,那就是。它们的存在是为了避免这种回调嵌套,也就是“回调地狱”@CharlieMartin,这都是关于控制流的。如果您希望代码中有不同的路径(分支),而“不继续链”正是这一点,那么您需要嵌套控制结构。然后带有两个回调的
是这里的一个控制结构。避免嵌套的唯一方法是使用async
/await
的早期返回,但我认为这不是更好的方法。这不是唯一的方法。您抛出一个自定义错误,然后在下一个错误回调中,您说“这是来自上面的自定义错误吗?抛出它并继续向下转发链。如果不是,请在这里处理它”。你不能停止链条,但根据传递给你的信息,你可以做出不同的行为。当您收到自定义错误时,通过不做任何操作来有效地“取消”链。每件事都可以用一根链条来完成。这就是Promise的美妙之处。我不知道这是否有意义,但请检查错误类型,并在下一个错误回调中相应地执行条件逻辑。这种逻辑可能意味着回归新的承诺,回到成功的轨道。这可能意味着,如果你在剩下的过程中这样做,除了重新抛出相同的错误(实际上是“取消”链)之外,什么也不做。或者这可能意味着抛出另一种错误。这可能意味着将一个错误添加到一个错误数组中,并在最后抛出该数组以同时处理所有错误。希望我能在这里放一个代码示例。你认为它很漂亮,我觉得很难看。一位专家说,创造这么多承诺是相当低效的
try {
var user = await getUser(userId);
} catch(error) {
crashreporter.reportError('User DB failed', error);
return showToUser(1);
}
try {
var chargeId = chargeCreditCard(user);
} catch(error) {
crashreporter.reportError('Credit card failed', error);
return showToUser(4);
}
try {
var chargeHistoryId = saveChargeId(chargeId);
} catch(error) {
crashreporter.reportError('ChargeId DB failed', error);
return showToUser(2);
}
try {
var result = associateChargeToUsers(chargeHistoryId);
} catch(error) {
crashreporter.reportError('chargeHistoryId DB failed', error);
return showToUser(3);
}
return showToUser(finalFormatting(result));