Javascript 在承诺链中处理多个捕获
我对promises还是相当陌生,目前正在使用bluebird,但是我有一个场景,我不太确定如何最好地处理它 例如,我在express应用程序中有一个承诺链,如下所示:Javascript 在承诺链中处理多个捕获,javascript,node.js,promise,bluebird,Javascript,Node.js,Promise,Bluebird,我对promises还是相当陌生,目前正在使用bluebird,但是我有一个场景,我不太确定如何最好地处理它 例如,我在express应用程序中有一个承诺链,如下所示: repository.Query(getAccountByIdQuery) .catch(function(error){ res.status(404).send({ error: "No account found with this Id" }); })
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
如果X做Y,否则Z
任何帮助都会很好。
.catch
的工作原理类似于try catch
语句,这意味着您只需要在结尾处使用一个catch:
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" });
}
});
此行为完全类似于同步抛出:
try{
throw new Error();
} catch(e){
// handle
}
// this code will run, since you recovered from the error!
这是.catch
-能够从错误中恢复的一半。可能需要重新切换以表明状态仍然是错误:
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
但是,在您的情况下,这一点是不起作用的,因为错误将被稍后的处理程序捕获。这里真正的问题是,一般来说,通用的“处理任何事情”错误处理程序都是一种不好的做法,在其他编程语言和生态系统中极不受欢迎。因此,Bluebird提供类型化和谓词捕获
另外一个优点是,您的业务逻辑根本不需要(也不应该)知道请求/响应周期。查询不负责决定客户端获得的HTTP状态和错误,稍后随着应用程序的增长,您可能希望将业务逻辑(如何查询数据库和如何处理数据)与发送给客户端的内容(什么HTTP状态代码、什么文本和什么响应)分开
下面是我将如何编写您的代码
首先,我将使用.Query
抛出NoSuchAccountError
,我将从Bluebird已经提供的Promise.OperationalError
中对其进行子类化。如果您不确定如何对错误进行子类化,请告诉我
另外,我还将其子类化为AuthenticationError
,然后执行如下操作:
function changePassword(queryDataEtc){
return repository.Query(getAccountByIdQuery)
.then(convertDocumentToModel)
.then(verifyOldPassword)
.then(changePassword);
}
正如你所看到的,它非常干净,你可以像阅读说明书一样阅读文本,了解过程中发生的事情。它也与请求/响应分开
现在,我将从路由处理程序中这样调用它:
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" });
});
这样,逻辑都在一个地方,如何处理客户机错误的决策都在一个地方,它们不会相互混乱
我想知道是否有一种方法可以让我根据错误以某种方式迫使链条在某一点停止
不,你不能真正“结束”一个链,除非你抛出一个异常,这个异常会一直冒泡到它的结束。请参阅以了解如何做到这一点
他的模式的派生不是区分错误类型,而是使用具有statusCode
和body
字段的错误,这些字段可以从单个通用.catch
处理程序发送。不过,根据您的应用程序结构,他的解决方案可能更简洁
或者如果有更好的方法来构造它,以获得某种形式的分支行为
是的,你能做到。但是,这意味着要离开该链并“返回”嵌套,就像在嵌套的if-else或try-catch语句中一样:
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" });
});
我一直这样做: 你最后留下你的渔获量。当错误发生在链的中间时,只需抛出一个错误
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" });
}
});
您的其他函数可能如下所示:
function convertDocumentToModel(resultOfQuery) {
if (!resultOfQuery){
throw new Error('no_account');
} else {
return new Promise(function(resolve) {
//do stuff then resolve
resolve(model);
}
}
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 */
});
代替.then().catch()…
您可以执行.then(resolveFunc,rejectFunc)
。如果你一路处理事情,这个承诺链会更好。下面是我将如何重写它:
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" });
}
}
);
注意:if(error!=null)
对于与最近的错误交互来说有点麻烦。我认为对于复杂的逻辑序列是最好的解决方案,但对于更简单的情况,这里是我的替代方案。我只是使用了一个errormeasted
标志和return Promise.reject()
来跳过任何后续的语句,然后是或catch
语句。所以看起来是这样的:
function convertDocumentToModel(resultOfQuery) {
if (!resultOfQuery){
throw new Error('no_account');
} else {
return new Promise(function(resolve) {
//do stuff then resolve
resolve(model);
}
}
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 */
});
如果您有两个以上的then/catch对,您可能应该使用Benjamin Grunbaum的解决方案。但这对于一个简单的设置是有效的
注意,最后的捕获
只有返回代码>而不是返回承诺。拒绝()
,因为没有后续的then
需要跳过,它将被视为未处理的承诺拒绝,节点不喜欢。如上所述,最终的catch
将返回一个和平解决的承诺。可能会晚一点到达派对,但可以嵌套.catch
,如下所示:
编辑:我提交了这个,因为它提供了一般要求的功能。然而,在这种特殊情况下,情况并非如此。因为正如其他人已经详细解释的那样,.catch
应该能够恢复错误。例如,您不能在多个.catch
回调中向客户端发送响应,因为没有显式返回的.catch
将解析为未定义的,在这种情况下,会导致继续。然后即使链未真正解析,也会触发,可能导致触发以下.catch
并向客户端发送另一个响应,从而导致错误