Handling multiple catches in promise chain
我仍然是相当新的承诺,目前正在使用蓝鸟,但我有一个场景,我不太确定如何最好地处理它。
例如,我在快递应用程序中有一个承诺链,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | repository.Query(getAccountByIdQuery) .catch(function(error){ res.status(404).send({ error:"No account found with this Id" }); }) .then(convertDocumentToModel) .then(verifyOldPassword) .catch(function(error) { res.status(406).send({ OldPassword: error }); }) .then(changePassword) .then(function(){ res.status(200).send(); }) .catch(function(error){ console.log(error); res.status(500).send({ error:"Unable to change password" }); }); |
所以我追求的行为是:
- 通过Id获取帐户
- 如果此时存在拒绝,则弹出并返回错误
- 如果没有错误,则将文档转换为模型
- 使用数据库文档验证密码
- 如果密码不匹配则弹出并返回不同的错误
- 如果没有错误则更改密码
- 然后返回成功
- 如果出现其他任何问题,请返回500
因此目前捕获似乎并没有停止链接,这是有道理的,所以我想知道是否有办法让我以某种方式迫使链条根据错误在某一点停止,或者是否有更好的方法构造它以获得某种形式的分支行为,因为存在
任何帮助都会很棒。
此行为与同步投掷完全相同:
1 2 3 4 5 6 | try{ throw new Error(); } catch(e){ // handle } // this code will run, since you recovered from the error! |
这是
1 2 3 4 5 6 7 | try{ throw new Error(); } catch(e){ // handle throw e; // or a wrapper over e so we know it wasn't handled } // this code will not run |
但是,由于错误会被后来的处理程序捕获,因此单独使用此功能不适用于您的情况。这里真正的问题是,通用的"HANDLE ANYTHING"错误处理程序通常是一种不好的做法,并且在其他编程语言和生态系统中非常不受欢迎。出于这个原因,Bluebird提供了类型和谓词捕获。
增加的优势是您的业务逻辑根本不(并且不应该)必须知道请求/响应周期。查询负责决定客户端获取哪种HTTP状态和错误,以后随着应用程序的增长,您可能希望将业务逻辑(如何查询数据库以及如何处理数据)与发送给客户端的内容分开(什么http状态代码,什么文本和什么响应)。
这是我编写代码的方式。
首先,我得
我另外将其子类化为
1 2 3 4 5 6 | function changePassword(queryDataEtc){ return repository.Query(getAccountByIdQuery) .then(convertDocumentToModel) .then(verifyOldPassword) .then(changePassword); } |
正如您所看到的 - 它非常干净,您可以阅读文本,就像过程中发生的事情的说明手册一样。它也与请求/响应分开。
现在,我将从路由处理程序中调用它:
1 2 3 4 5 6 7 8 9 10 | changePassword(params) .catch(NoSuchAccountError, function(e){ res.status(404).send({ error:"No account found with this Id" }); }).catch(AuthenticationError, function(e){ res.status(406).send({ OldPassword: error }); }).error(function(e){ // catches any remaining operational errors res.status(500).send({ error:"Unable to change password" }); }).catch(function(e){ res.status(500).send({ error:"Unknown internal server error" }); }); |
这样,逻辑就在一个地方,如何处理客户端错误的决定都集中在一个地方,并且它们不会相互混乱。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | repository.Query(getAccountByIdQuery) .then(convertDocumentToModel) .then(verifyOldPassword) .then(changePassword) .then(function(){ res.status(200).send(); }) .catch(function(error) { if (/*see if error is not found error*/) { res.status(404).send({ error:"No account found with this Id" }); } else if (/*see if error is verification error*/) { res.status(406).send({ OldPassword: error }); } else { console.log(error); res.status(500).send({ error:"Unable to change password" }); } }); |
I am wondering if there is a way for me to somehow force the chain to stop at a certain point based upon the errors
不可以。你不能真正"结束"一个链,除非你抛出一个泡沫直到结束的例外。请参阅Benjamin Gruenbaum关于如何做到这一点的答案。
他的模式的推导不是要区分错误类型,而是使用具有
or if there is a better way to structure this to get some form of branching behaviour
是的,你可以用promises做分支。但是,这意味着离开链并"返回"嵌套 - 就像你在嵌套的if-else或try-catch语句中所做的那样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | repository.Query(getAccountByIdQuery) .then(function(account) { return convertDocumentToModel(account) .then(verifyOldPassword) .then(function(verification) { return changePassword(verification) .then(function() { res.status(200).send(); }) }, function(verificationError) { res.status(406).send({ OldPassword: error }); }) }, function(accountError){ res.status(404).send({ error:"No account found with this Id" }); }) .catch(function(error){ console.log(error); res.status(500).send({ error:"Unable to change password" }); }); |
我一直这样做:
你最后留下了你的渔获物。当它发生在你的链中间时发出错误。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | repository.Query(getAccountByIdQuery) .then((resultOfQuery) => convertDocumentToModel(resultOfQuery)) //inside convertDocumentToModel() you check for empty and then throw new Error('no_account') .then((model) => verifyOldPassword(model)) //inside convertDocumentToModel() you check for empty and then throw new Error('no_account') .then(changePassword) .then(function(){ res.status(200).send(); }) .catch((error) => { if (error.name === 'no_account'){ res.status(404).send({ error:"No account found with this Id" }); } else if (error.name === 'wrong_old_password'){ res.status(406).send({ OldPassword: error }); } else { res.status(500).send({ error:"Unable to change password" }); } }); |
您的其他功能可能如下所示:
1 2 3 4 5 6 7 8 9 | function convertDocumentToModel(resultOfQuery) { if (!resultOfQuery){ throw new Error('no_account'); } else { return new Promise(function(resolve) { //do stuff then resolve resolve(model); } } |
派对可能有点晚了,但是可以嵌套
Mozilla开发者网络 - 使用Promises
编辑:我提交了这个,因为它提供了一般的问题功能。 但是在这种特殊情况下并不存在。 因为已经有其他人详细解释过,
我认为Benjamin Gruenbaum上面的答案是复杂逻辑序列的最佳解决方案,但这是我对更简单情况的替代方案。我只是使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | let errorEncountered = false; someCall({ /* do stuff */ }) .catch({ /* handle error from someCall*/ errorEncountered = true; return Promise.reject(); }) .then({ /* do other stuff */ /* this is skipped if the preceding catch was triggered, due to Promise.reject */ }) .catch({ if (errorEncountered) { return; } /* handle error from preceding then, if it was executed */ /* if the preceding catch was executed, this is skipped due to the errorEncountered flag */ }); |
如果你有两个以上/ catch对,你应该使用Benjamin Gruenbaum的解决方案。但这适用于简单的设置。
请注意,最终的
而不是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | repository.Query(getAccountByIdQuery) .then( convertDocumentToModel, () => { res.status(404).send({ error:"No account found with this Id" }); return Promise.reject(null) } ) .then( verifyOldPassword, () => Promise.reject(null) ) .then( changePassword, (error) => { if (error != null) { res.status(406).send({ OldPassword: error }); } return Promise.Promise.reject(null); } ) .then( _ => res.status(200).send(), error => { if (error != null) { console.error(error); res.status(500).send({ error:"Unable to change password" }); } } ); |
注意: