我有一个使用MongoDB,Node.js和Express的REST API向我的NoSQL DB发出请求,并且根据不同的结果,我想区分我发送给客户的错误。
我的代码的当前版本有一个通用错误处理程序,并始终向客户端发送相同的错误消息:
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);
});
我个人认为这个解决方案特别有吸引力,因为这意味着我的if then else
块中会有一个巨大的catch
语句链。
另外,正如前一篇文章中所提到的,通用错误处理程序通常不受欢迎(并且有充分的理由)。
如何改进此代码?
答案 0 :(得分:2)
当我开始这个主题时,我有两个目标:
if then else
厄运我现在提出了两个截然不同的解决方案,我现在在这里发布,供将来参考。
此解决方案基于@Marc Rohloff的解决方案,但是,我没有一个函数数组并循环遍历每个函数,而是有一个包含所有错误的对象。
这种方法更好,因为它更快,并且不需要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
厄运。此解决方案仅具有部分错误区分。原因是您只能通过throw {reaon: "reasonHere", error: "errorHere"}
机制区分您专门构建的错误。
在这个例子中,您将能够知道文档是否已经存在,但是如果保存所述文档时出现错误(比如说,验证文档),那么它将被视为“通用”错误并抛出为一个500.
要实现完全错误区分,您必须使用nested Promise anti pattern,如下所示:
.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}});
})
它会起作用......但我认为这是避免反模式的最佳做法。
co
。此解决方案通过库Generators使用co
。意味着在不久的将来使用类似于async/await
的语法替换Promise这个新功能允许您编写读取类似同步的异步代码(好吧,差不多)。
要使用它,首先需要安装co
或类似ogen之类的内容。我非常喜欢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
所有对库的Promise,它将解析它们并相应地返回或抛出。
使用此策略,您可以像编写同步代码一样有效地编写代码(使用yield
除外)。
我个人更喜欢这种策略,因为:
return
BREAK 代码流。这在承诺链中是不可能的,因为在那些你必须强制throw
(很多次是无意义的)并抓住它停止执行的那些。生成器函数只有在传递到库co
后才会执行,然后返回一个Promise,说明执行是否成功。
此解决方案实现:
if then else
地狱和广义捕获者(虽然您将在代码中使用try/catch
,但如果需要,您仍然可以访问广义捕手。)在我看来,使用生成器更灵活,更易于阅读代码。并非所有情况都是发电机使用的情况(如视频中的mpj建议),但在这种特殊情况下,我认为这是最好的选择。
解决方案1 :对问题采取良好的经典方法,但有承诺链接所固有的问题。你可以通过嵌套承诺来克服其中的一些,但这是一种反模式并且无法实现它们的目的。
解决方案2 :功能更多,但需要一个库和生成器如何工作的知识。此外,不同的库会有不同的行为,所以你应该意识到这一点。
答案 1 :(得分:0)
我认为一个好的改进是创建一个错误实用程序方法,它将错误消息作为参数,然后执行所有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;
}