Javascript 实现承诺中的错误区分 背景

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);

我有一个使用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);    
            });
    });
这是一个问题,因为客户端总是会收到相同的错误消息,无论发生什么,我都需要区分错误

研究 根据逻辑和错误/响应处理的划分,我找到了一个可能的解决方案:

但是,有几件事我不明白:

  • 至少在我的示例中,我不知道如何将逻辑与响应分开。响应将取决于逻辑
  • 我希望避免错误的子分类和层次结构。首先是因为我不使用bluebird,而且我不能按照答案的建议对错误类进行子类化,其次是因为我不希望我的代码包含十亿个不同的错误类,这些错误类具有脆弱的层次结构,将来会发生变化
  • 我的想法是,我两个都不喜欢 使用这种结构,如果我想要区分错误,我唯一能做的就是检测发生的错误,用该信息构建一个对象,然后抛出它:

    .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
  • 我现在提出了两个截然不同的解决方案,我现在把它们贴在这里,以供将来参考

    解决方案1:带有Errors对象的通用错误处理程序 这个解决方案是基于的解决方案,但是,我没有一个数组的函数和循环通过每一个,我有一个对象与所有的错误

    这种方法更好,因为它更快,并且不需要进行
    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
    语句返回预期结果,则没有问题。也许可以问一个问题?看起来你