背景
我使用MongoDB的,Node.js的和快速,使一个请求我的NoSQL数据库,并根据不同的结果,我想区分我送这错误顾客一个REST API。
问题
我的代码的当前版本有一个通用的错误处理程序,并始终发送相同的错误信息给客户端:
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
块。
此外,如在前面的文章中提到,一般错误处理程序通常在(和一个很好的理由IMO)皱起了眉头。
问题
我怎样才能改善这种代码?
目标
当我开始这个线程,我心中有两个目标:
- 有误差分化
- 避免的
if then else
厄运的通用捕手
我现在已经提出了两个根本不同的解决方案,我现在在这里发布,以供将来参考。
解决方法1:使用错误的对象一般错误处理程序
该解决方案是基于从溶液@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。
为了实现这一完整的错误分化,你将不得不使用嵌套无极反面模式如下所示:
.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:通过使用ECMA6发电机co
。
该解决方案使用发电机通过图书馆co
。 指具有类似的语法替换不久的将来承诺async/await
这个新功能可以让您编写异步代码,倒像是同步的(当然,几乎)。
要使用它,你首先需要安装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
所有的承诺到库,这将解决这些问题,并返还或相应扔。
使用这种策略,你的代码有效喜欢你的编码同步码(除了使用的yield
)。
我个人比较喜欢这种策略,因为:
- 您的代码易于阅读,它看起来同步(同时仍具有异步代码的优势)。
- 你不必建立和抛出Error对象的每一个地方,你可以简单地立即发送消息。
- 而且,您可以通过BREAK代码流
return
。 这是不可能的承诺链,在那些你必须强制throw
(多次无意义的一个),并抓住它停止执行。
发电机功能将只执行一次传递到图书馆co
,然后返回一个承诺,称如果执行成功与否。
该解决方案实现了:
- 错误分化
- 避免了
if then else
地狱和广义捕手(虽然你会使用try/catch
在你的代码,你仍然有机会获得一个广义的捕手,如果你需要一个)。
使用发电机,在我看来,更灵活,使得更容易阅读的代码。 并非所有的情况都是发电机的使用情况下(如在MPJ视频显示),但在这种特殊情况下,我认为这是最好的选择。
结论
方案1:良好的传统方法的问题,而是有内在的承诺链接的问题。 您可以通过嵌套承诺克服其中的一些,但是这是一个反模式,违背了他们的目的。
方案2:更灵活,但需要对发电机的工作原理库和知识。 此外,不同的库都会有不同的行为,所以你应该意识到这一点。
我认为一个良好的改善将产生一个错误应用方法,它采用错误消息作为参数,然后做你所有的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;
}