实现承诺误差分化(Achieve error differentiation in Promises

2019-09-26 08:16发布

背景

我使用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);    
            });
    });

这是一个问题,因为客户端总是会得到同样的错误信息,不管是什么,我需要错误分化!

研究

我发现了一个可能的解决方案的基础上,逻辑和错误/响应处理的划分:

  • 在处理许链的多个渔获

不过,我不明白几件事情:

  1. 我不知道怎么样,至少在我的例子,我可以逻辑从响应分开。 响应将取决于逻辑毕竟!
  2. 我想,以避免错误子类和层次结构。 首先,因为我不使用蓝鸟,我不能继承错误类的回答表明,其次是因为我不希望我与脆层次结构,将在未来改变数十亿不同的错误类代码。

我的想法,我不很喜欢要么

采用这种结构,如果我想误差分化,我能做的唯一的事情是检测出现错误,利用这些信息建立一个对象,然后把它:

.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)皱起了眉头。

问题

我怎样才能改善这种代码?

Answer 1:

目标

当我开始这个线程,我心中有两个目标:

  1. 有误差分化
  2. 避免的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)}`);
    };

该解决方案实现了:

  1. 部分错误分化(我会解释为什么)
  2. 避免了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);

发电机功能requestHandleryield所有的承诺到库,这将解决这些问题,并返还或相应扔。

使用这种策略,你的代码有效喜欢你的编码同步码(除了使用的yield )。

我个人比较喜欢这种策略,因为:

  1. 您的代码易于阅读,它看起来同步(同时仍具有异步代码的优势)。
  2. 不必建立和抛出Error对象的每一个地方,你可以简单地立即发送消息。
  3. 而且,您可以通过BREAK代码流return 。 这是不可能的承诺链,在那些你必须强制throw (多次无意义的一个),并抓住它停止执行。

发电机功能将只执行一次传递到图书馆co ,然后返回一个承诺,称如果执行成功与否。

该解决方案实现了:

  1. 错误分化
  2. 避免了if then else地狱和广义捕手(虽然你会使用try/catch在你的代码,你仍然有机会获得一个广义的捕手,如果你需要一个)。

使用发电机,在我看来,更灵活,使得更容易阅读的代码。 并非所有的情况都是发电机的使用情况下(如在MPJ视频显示),但在这种特殊情况下,我认为这是最好的选择。

结论

方案1:良好的传统方法的问题,而是有内在的承诺链接的问题。 您可以通过嵌套承诺克服其中的一些,但是这是一个反模式,违背了他们的目的。

方案2:更灵活,但需要对发电机的工作原理库和知识。 此外,不同的库都会有不同的行为,所以你应该意识到这一点。



Answer 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;
}


文章来源: Achieve error differentiation in Promises