Javascript的承诺模式 – 区分错误

考虑这个代码块:

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

显然,这个问题是THEN(USER)块在用户DB失败的情况下被执行。 另一个select是将第一个catch块移动到链的末尾。 但是,这会导致另一个问题! 我们将无法区分错误是来自User DB还是CreditCard。

下面的模式,我认为解决问题,认为是一个承诺反模式? 有没有更好的方法来做到这一点? 我看到的问题是,你可以在半callback地狱。

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

编辑:我想我不是很清楚。 如果有更多THEN块,如下所示。 问题是,一旦你碰到一个错误,你不希望链条继续。

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

下面的模式,我认为解决了这个问题,认为是一个承诺反模式?

不,还好。

有没有更好的方法来做到这一点?

是的,看看.then(…).catch(…).then(…).catch(…)之间的区别 。 如果要严格区分成功案例(继续)和错误案例(报告这个具体问题),那么传递两个callback就是一个好主意。 这样,外部处理程序不能由成功案例代码中的失败触发,只能从安装失败中触发。 在你的情况下:

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

尽pipe您可能想使用通用的error handling程序:

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

在这里,每一个callback函数都会返回一个承诺,自己拒绝相应的错误。 理想情况下,每个被调用的函数都已经这样做了,当他们不这样做时,你需要为每个函数添加一个特定的catch ,你可能需要使用一个wrapper helper函数(也许是crashreporter一部分)。

 function withCrashReporting(fn, msg, n) { return function(x) { return fn(x) .catch(function(error){ crashreporter.reportError(msg, error); throw new Error(n); }); }; } withCrashReporting(getUser, 'User DB failed', 1)(userId) .then(withCrashReporting(chargeCreditCard, 'Credit card failed', 4)) .then(withCrashReporting(saveChargeId, 'ChargeId DB failed', 2)) .then(withCrashReporting(associateChargeToUsers, 'chargeHistoryId DB failed', 3)) .then(finalFormatting, function(error) { return error.message; }) .then(showToUser); 

我看到的问题是,你可以在半callback地狱。

不,这只是一个适当的包装水平。 与callback地狱形成鲜明对比的是,它可以平铺到两个最大的嵌套,它总是有一个返回值。

如果你绝对要避免嵌套和callback,使用async / await ,但实际上更丑陋的:

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

你应该只有一个catch你的链,但你可以添加更多的上下文到每个函数中引发的错误。 例如,当chargeCreditCard出现错误情况时,您可以根据您要报告的内容添加错误message属性。 然后在你的catcherror handling程序中,你可以把这个message属性传递给记者:

 getUser(userId) .then(chargeCreditCard) .catch(reportError); function reportError(error) { crashreporter.reportError(error.message, error); } 

这里是如何完成“取消”链,而不是嵌套callback。 这就是为什么承诺是“回拨地狱”的解决scheme…

 var errors = { USER: 'User DB failed', CHARGE: 'ChargeId DB failed', HISTORY: 'chargeHistoryId DB failed', }; function onError(type, error) { throw { message: type, error: error } } getUser(userId) .then(chargeCreditCard, onError.bind(null, errors.USER)) .then(saveChargeId, function (error) { if (error.message === errors.USER) throw error else onError(errors.CHARGE, error); }) .then(associateChargeToUsers, function(error) { if (error.message === errors.CHARGE || error.message === errors.USER) throw error else onError(errors.HISTORY, error); }) .then(finalFormatting, function(error) { crashreporter.reportError(error.message, error.error); }) .then(showToUser); 

从本质上讲,如果第一个承诺失败了,你将错误传递给失败callback链,当它到达最后,它被logging下来。 没有其他的承诺被创build,所以你在第一次失败时有效地“取消”了操作。