Javascript 实现承诺中的错误区分 背景
我有一个使用MongoDB、Node.js和Express的REST API,它向我的NoSQL DB发出请求,根据不同的结果,我想区分我向客户发送的错误 问题 “我的代码”的当前版本具有通用错误处理程序,并且始终向客户端发送相同的错误消息:Javascript 实现承诺中的错误区分 背景,javascript,node.js,rest,express,promise,Javascript,Node.js,Rest,Express,Promise,我有一个使用MongoDB、Node.js和Express的REST API,它向我的NoSQL DB发出请求,根据不同的结果,我想区分我向客户发送的错误 问题 “我的代码”的当前版本具有通用错误处理程序,并且始终向客户端发送相同的错误消息: api.post("/Surveys/", (req, res) => { const surveyJSON = req.body; const sender = replyFactory(res);
api.post("/Surveys/", (req, res) => {
const surveyJSON = req.body;
const sender = replyFactory(res);
Survey.findOne({_id: surveyJSON.id})
.then(doc => {
if(doc !== null)
throw {reason: "ObjectRepeated"};
//do stuff
return new Survey(surveyJSON).save();
})
.then(() => sender.replySuccess("Object saved with success!"))
.catch(error => {
/*
* Here I don't know if:
* 1. The object is repeated
* 2. There was an error while saving (eg. validation failed)
* 3. Server had a hiccup (500)
*/
sender.replyBadRequest(error);
});
});
这是一个问题,因为客户端总是会收到相同的错误消息,无论发生什么,我都需要区分错误
研究
根据逻辑和错误/响应处理的划分,我找到了一个可能的解决方案:
.then(doc => {
if(doc === null)
throw {reason: "ObjectNotFound"};
//do stuff
return doc.save();
})
.catch(error => {
if(error.reason === "ObjectNotFound")
sendJsonResponse(res, 404, err);
else if(error.reason === "Something else ")
sendJsonResponse(/*you get the idea*/);
else //if we don't know the reasons, its because the server likely crashed
sendJsonResponse(res, 500, err);
});
我个人并不觉得这个解决方案特别有吸引力,因为它意味着我的catch
块中将有一个巨大的if-then-else
语句链
此外,正如前一篇文章中提到的,一般错误处理程序通常是不受欢迎的(这在我看来是有充分理由的)
问题
如何改进此代码?我认为一个很好的改进是创建一个错误实用程序方法,该方法将错误消息作为参数,然后执行所有ifs以尝试解析错误(必须在某个地方发生的逻辑)并返回格式化错误
function errorFormatter(errMsg) {
var formattedErr = {
responseCode: 500,
msg: 'Internal Server Error'
};
switch (true) {
case errMsg.includes('ObjectNotFound'):
formattedErr.responseCode = 404;
formattedErr.msg = 'Resource not found';
break;
}
return formattedErr;
}
目标
当我开始这篇文章时,我有两个目标:
if-then-else
if
验证,这意味着您实际上执行的逻辑更少:
const errorHandlers = {
ObjectRepeated: function(error){
return { code: 400, error };
},
SomethingElse: function(error){
return { code: 499, error };
}
};
Survey.findOne({
_id: "bananasId"
})
.then(doc => {
//we dont want to add this object if we already have it
if (doc !== null)
throw { reason: "ObjectRepeated", error:"Object could not be inserted because it already exists."};
//saving empty object for demonstration purposes
return new Survey({}).save();
})
.then(() => console.log("Object saved with success!"))
.catch(error => {
respondToError(error);
});
const respondToError = error => {
const errorObj = errorHandlers[error.reason](error);
if (errorObj !== undefined)
console.log(`Failed with ${errorObj.code} and reason ${error.reason}: ${JSON.stringify(errorObj)}`);
else
//send default error Obj, server 500
console.log(`Generic fail message ${JSON.stringify(error)}`);
};
此解决方案可实现:
if-then-else
抛出{reaon:“reasonHere”,error:“errorHere”}
机制来区分专门构建的错误
在本例中,您将能够知道文档是否已经存在,但是如果保存所述文档时出错(比如说,验证错误),那么它将被视为“一般”错误并作为500抛出
要使用此功能实现完全的错误区分,您必须使用以下类似方法:
.then(doc => {
//we dont want to add this object if we already have it
if (doc !== null)
throw { reason: "ObjectRepeated", error:"Object could not be inserted because it already exists." };
//saving empty object for demonstration purposes
return new Survey({}).save()
.then(() => {console.log("great success!");})
.catch(error => {throw {reason: "SomethingElse", error}});
})
这会有用的。。。但我认为这是避免反模式的最佳实践
解决方案2:通过co
使用ECMA6发电机。
此解决方案通过库使用。为了在不久的将来用类似于async/await
的语法取代承诺,这个新特性允许您编写异步代码,读起来像是同步的(几乎是同步的)
要使用它,首先需要安装co
,或者类似的东西。我更喜欢co,所以这就是我将在这里使用的
const requestHandler = function*() {
const survey = yield Survey.findOne({
_id: "bananasId"
});
if (survey !== null) {
console.log("use HTTP PUT instead!");
return;
}
try {
//saving empty object for demonstration purposes
yield(new Survey({}).save());
console.log("Saved Successfully !");
return;
}
catch (error) {
console.log(`Failed to save with error: ${error}`);
return;
}
};
co(requestHandler)
.then(() => {
console.log("finished!");
})
.catch(console.log);
生成器函数requestHandler
将向库生成所有承诺,库将解析它们并相应地返回或抛出
使用此策略,您可以像编写同步代码一样有效地编写代码(除了使用yield
)
我个人更喜欢这种策略,因为:
您的代码易于阅读并且看起来是同步的(同时仍然具有异步代码的优点)
您不必到处构建和抛出错误对象,只需立即发送消息即可
您还可以通过return
中断代码流。这在承诺链中是不可能的,因为在那些承诺链中,您必须强制抛出一个并捕获它以停止执行
生成器函数只会在传递到库co
后执行,然后该库返回一个承诺,说明执行是否成功
此解决方案可实现:
误差微分
避免使用if-then-else
hell和通用捕获器(尽管您将在代码中使用try/catch
,如果需要,您仍然可以访问通用捕获器)
在我看来,使用生成器更灵活,更易于阅读代码。并非所有情况下都是发电机使用情况(如mpj在视频中建议),但在这个特定情况下,我相信这是最好的选择
结论
解决方案1:解决问题的经典方法,但存在承诺链固有的问题。你可以通过嵌套承诺来克服其中的一些问题,但这是一种反模式,违背了它们的目的
解决方案2:更通用,但需要一个库和发电机工作原理方面的知识。此外,不同的库会有不同的行为,因此您应该意识到这一点。如何改进此代码?什么是改进?如果if..else if
语句返回预期结果,则没有问题。也许可以问一个问题?看起来你