实现Promises中的错误区分 [英] Achieve error differentiation in Promises

查看:66
本文介绍了实现Promises中的错误区分的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个使用MongoDB,Node.js和Express的REST API,该API向我的NoSQL DB发出请求,并且根据不同的结果,我想区分发送客户的错误.

I have a REST API using MongoDB, Node.js and Express that makes a request to my NoSQL DB and depending on different results, I want to differentiate which error I send the customer.

我的代码的当前版本具有通用错误处理程序,并始终将相同的错误消息发送给客户端:

The current version of my code has a generic error handler and always sends the same error message to the client:

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

这是一个问题,因为无论发生什么事,客户端都将始终收到相同的错误消息,而且我需要进行错误区分!

This is a problem, because the client will always get the same error message, no matter what and I need error differentiation!

基于逻辑和错误/响应处理的划分,我找到了一种可能的解决方案:

I found a possible solution, based on the division of logic and error/response handling:

但是,我不明白一些事情:

However, I don't understand a few things:

  1. 至少在我的示例中,我不知道如何将逻辑与响应分开.响应将完全取决于逻辑
  2. 我想避免错误子分类和层次结构.首先,因为我不使用bluebird,并且不能将错误类归类为答案,其次,其次,因为我不希望我的代码具有十亿种不同的错误类,并且它们的脆性层次结构将来会改变. li>
  1. I don't see how, at least in my example, I can separate the logic from the response. The response will depend on the logic after all!
  2. I would like to avoid error sub-classing and hierarchy. First because I don't use bluebird, and I can't subclass the error class the answer suggests, and second because I don't want my code with a billion different error classes with brittle hierarchies that will change in the future.

我的想法是,我也不喜欢

使用这种结构,如果我想区分错误,我唯一能做的就是检测到发生的错误,使用该信息构建一个对象,然后将其抛出:

My idea, that I don't really like either

With this structure, if I want error differentiation, the only thing I can do is to detect an error occurred, build an object with that information and then throw it:

.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语句链.

I personally don't find this solution particularly attractive because it means I will have a huge if then else chain of statements in my catch block.

而且,如前一篇文章中所述,一般错误处理程序通常不受欢迎(并且有一个很好的理由imo).

Also, as mentioned in the previous post, generic error handlers are usually frowned upon (and for a good reason imo).

如何改进此代码?

推荐答案

目标

启动此线程时,我想到了两个目标:

Objectives

When I started this thread, I had two objectives in mind:

  1. 具有错误区分能力
  2. 避免通用捕获器中的if then else厄运

我现在提出了两种截然不同的解决方案,我现在将其发布在这里,以供将来参考.

I have now come up with two radically distinct solutions, which I now post here, for future reference.

此解决方案基于 @Marc Rohloff 中的解决方案,而不是具有数组功能并遍历每个对象,我有一个带有所有错误的对象.

This solution is based on the solution from @Marc Rohloff, however, instead of having an array of functions and looping through each one, I have an object with all the errors.

这种方法更好,因为它更快,并且不需要进行if验证,这意味着您实际上不需要做更多的逻辑:

This approach is better because it is faster, and removes the need for the if validation, meaning you actually do less logic:

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毁灭.
  1. Partial error differentiation (I will explain why)
  2. Avoids an if then else of doom.

此解决方案仅具有部分错误区分功能.这样做的原因是,您只能通过throw {reaon: "reasonHere", error: "errorHere"}机制来区分您专门构建的错误.

This solution only has partial error differentiation. The reason for this is because you can only differentiate errors that you specifically build, via the throw {reaon: "reasonHere", error: "errorHere"} mechanism.

在此示例中,您将能够知道该文档是否已存在,但是如果保存该文档时发生错误(例如,验证一个),则将其视为一般"错误并抛出为500.

In this example, you would be able to know if the document already exists, but if there is an error saving the said document (lets say, a validation one) then it would be treated as "Generic" error and thrown as a 500.

要对此进行完全错误区分,您必须使用嵌套的Promise反模式,如下所示:

To achieve full error differentiation with this, you would have to use the nested Promise anti pattern like the following:

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

它可以工作...但是我认为这是避免使用反模式的最佳实践.

It would work... But I see it as a best practice to avoid anti-patterns.

此解决方案通过库生成器 /github.com/tj/co"rel =" nofollow noreferrer> co .打算在不久的将来用类似于async/await的语法替换Promises,此新功能使您可以编写异步代码,读起来像同步(好吧,几乎).

This solution uses Generators via the library co. Meant to replace Promises in near future with a syntax similar to async/await this new feature allows you to write asynchronous code that reads like synchronous (well, almost).

要使用它,首先需要安装co或类似的 ogen 之类的东西.我非常喜欢co,所以这就是我将在这里使用的.

To use it, you first need to install co, or something similar like ogen. I pretty much prefer co, so that is what i will be using here instead.

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所有Promises发送到库,库将解析它们并相应地返回或抛出.

The generator function requestHandler will yield all Promises to the library, which will resolve them and either return or throw accordingly.

使用此策略,您可以像编码同步代码一样有效地进行编码(使用yield除外).

Using this strategy, you effectively code like you were coding synchronous code (except for the use of yield).

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

I personally prefer this strategy because:

  1. 您的代码易于阅读并且看起来是同步的(尽管仍然具有异步代码的优点).
  2. 不必必须在任何地方构建和抛出错误对象,您只需立即发送消息即可.
  3. 而且,您可以通过return BREAK 代码流.在承诺链中这是不可能的,因为在那些情况下,您必须强制throw(很多时候是无意义的)并抓住它停止执行.
  1. Your code is easy to read and it looks synchronous (while still have the advantages of asynchronous code).
  2. You do not have to build and throw error objects every where, you can simply send the message immediately.
  3. And, you can BREAK the code flow via return. This is not possible in a promise chain, as in those you have to force a throw (many times a meaningless one) and catch it to stop executing.

生成器函数仅在传递到库co中后才执行,该库随后返回一个Promise,指出执行是否成功.

The generator function will only be executed once passed into the library co, which then returns a Promise, stating if the execution was successful or not.

此解决方案可以实现:

  1. 区分错误
  2. 避免使用if then else地狱和广义捕获器(尽管您将在代码中使用try/catch,并且如果需要,您仍可以使用广义捕获器).
  1. error differentiation
  2. avoids if then else hell and generalized catchers (although you will use try/catch in your code, and you still have access to a generalized catcher if you need one).

在我看来,使用生成器更加灵活,并且更易于阅读代码.并非所有情况都是发电机使用情况(如视频中的mpj建议),但在这种特定情况下,我认为这是最佳选择.

Using generators is, in my opinion, more flexible and makes for easier to read code. Not all cases are cases for generator usage (like mpj suggests in the video) but in this specific case, I believe it to be the best option.

解决方案1 ​​:解决问题的经典方法,但具有承诺链固有的问题.您可以通过嵌套promise来克服其中的一些问题,但这是一种反模式,破坏了它们的目的.

Solution 1: good classical approach to the problem, but has issues inherent to promise chaining. You can overcome some of them by nesting promises, but that is an anti pattern and defeats their purpose.

解决方案2 :功能更多,但需要具备有关生成器工作原理的库和知识.此外,不同的库将具有不同的行为,因此您应该意识到这一点.

Solution 2: more versatile, but requires a library and knowledge on how generators work. Furthermore, different libraries will have different behaviors, so you should be aware of that.

这篇关于实现Promises中的错误区分的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆