简体   繁体   English

在nodeJs中避免回调地狱/将变量传递给内部函数

[英]Avoiding callback hell in nodeJs / Passing variables to inner functions

Here's an example of something I'd like to simplify: 这是我想简化的一个例子:

exports.generateUrl = function (req, res) {
    var id = req.query.someParameter;

    var query = MyMongooseModel.findOne({'id': id});
    query.exec(function (err, mongooseModel) {
        if(err) {
            //deal with it
        }

        if (!mongooseModel) {
            generateUrl(Id,
                function (err, text, url) {
                    if (err) {
                        res.status(HttpStatus.INTERNAL_SERVER_ERROR).send(err);
                        return;
                    }
                    var newMongooseModel = new AnotherMongooseModel();
                    newMongooseModel.id = id;

                    newMongooseModel.save(function (err) {
                        if (err) {
                            res.status(HttpStatus.INTERNAL_SERVER_ERROR).send(err);
                        } else {
                            res.send({url: url, text: text});
                        }
                    });
                });
        } else {
            //deal with already exists
        }
    });
};

I've seen other SO answer where they tell you to use named functions, but don't say how to deal with variable you want to pass in or use jQuery's queue. 我已经看到了其他SO的答案,他们告诉你使用命名函数,但是没有说明如何处理你想要传入的变量或者使用jQuery的队列。 I do not have the luxury of either. 我也没有奢侈品。

I understand that I can replace my anonymous functions with names functions, but then I would need to pass arounds variables. 我知道我可以用名称函数替换我的匿名函数,但是我需要传递变量。 How would my inner function access res for instance if the function is defined elsewhere? 如果函数在别处定义,我的内部函数如何访问res

The core to your question is: 你问题的核心是:

I understand that I can replace my anonymous functions with names functions, but then I would need to pass arounds variables. 我知道我可以用名称函数替换我的匿名函数,但是我需要传递变量。 How would my inner function access res for instance if the function is defined elsewhere? 如果函数在别处定义,我的内部函数如何访问res?

The answer is to use a function factory. 答案是使用功能工厂。

In general, this: 一般来说,这个:

function x (a) {
    do_something(function(){
        process(a);
    });
}

can be converted to this: 可以转换为:

function x (a) {
    do_something(y_maker(a)); // notice we're calling y_maker,
                              // not passing it in as callback
}

function y_maker (b) {
    return function () {
        process(b);
    };
}

In the code above, y_maker is a function that generates a function (let's call that function's purpose "y"). 在上面的代码中, y_maker是一个生成函数的函数(让我们调用该函数的目的“y”)。 In my own code, I use the naming convention .._maker or generate_.. to denote that I'm calling a function factory. 在我自己的代码中,我使用命名约定.._makergenerate_..来表示我正在调用函数工厂。 But that's just me and the convention is in no way standard or widely adopted in the wild. 但这只是我而且这个惯例绝不是标准的,也不是广泛采用的。

So for your code you can refactor it to: 因此,对于您的代码,您可以将其重构为:

exports.generateUrl = function (req, res) {
    var id = req.query.someParameter;

    var query = MyMongooseModel.findOne({'id': id});
    query.exec(make_queryHandler(req,res));
};

function make_queryHandler (req, res) {
    return function (err, mongooseModel) {
        if(err) {
            //deal with it
        }
        else if (!mongooseModel) {
            generateUrl(Id,make_urlGeneratorHandler(req,res));
        } else {
            //deal with already exists
        }
}}

function make_urlGeneratorHandler (req, res) {
    return function (err, text, url) {
        if (err) {
            res.status(HttpStatus.INTERNAL_SERVER_ERROR).send(err);
            return;
        }
        var newMongooseModel = new AnotherMongooseModel();
        newMongooseModel.id = id;
        newMongooseModel.save(make_modelSaveHandler(req,res));
}}

function make_modelSaveHandler (req, res) {
    return function (err) {
        if (err) res.status(HttpStatus.INTERNAL_SERVER_ERROR).send(err);
        else res.send({url: url, text: text});
}}

This flattens out the nested callbacks. 这使得嵌套回调变得扁平化。 As an additional benefit, you get to properly name what the function is supposed to do. 作为额外的好处,您可以正确命名函数应该执行的操作。 Which I consider good practice. 我认为这是好的做法。

It also has the added advantage that it is significantly faster than when using anonymous callback (either with nesting callbacks or with promises, though if you pass named functions to promise.then() instead of anonymous functions then you'll get the same speed up benefits). 它还有一个额外的好处,它比使用匿名回调(使用嵌套回调或使用promises)快得多,但如果你将命名函数传递给promise.then()而不是匿名函数,那么你将获得相同的加速福利)。 A previous SO question (my google-fu is failing me today) found that named functions are more than twice the speed (if I remember correctly it was more than 5 times faster) of anonymous functions in node.js. 之前的SO问题(我的google-fu今天让我失败了)发现命名函数的速度是node.js中匿名函数的两倍以上(如果我没记错的话,速度提高了5倍多)。

Use promises. 使用承诺。 Using Q and mongoose-q it would give: something like that: 使用Q和mongoose-q它会给出:类似的东西:

exports.generateUrl = function (req, res) {
    var id = req.query.someParameter;
    var text = "";

    var query = MyMongooseModel.findOne({'id': id});
    query.execQ().then(function (mongooseModel) {

        if (!mongooseModel) {
            return generateUrl(Id)

     }).then(function (text) {
       var newMongooseModel = new AnotherMongooseModel();
       newMongooseModel.id = id;
       text = text;

       newMongooseModel.saveQ()
     }).then(function (url) {
        res.send({url: url, text: text});
     }).fail(function(err) {
        res.status(HttpStatus.INTERNAL_SERVER_ERROR).send(err);
     });
};

Named functions will be executed within the same scope that the anonymous functions are and would have access to all of variables you are currently using. 命名函数将在匿名函数所在的范围内执行,并且可以访问您当前使用的所有变量。 This approach would make your code less nested and more readable (which is good) but would still technically be in "callback hell". 这种方法可以使你的代码更少嵌套,更易读(这很好),但技术上仍然是“回调地狱”。 The best way to avoid situations like this is to wrap your asynchronous libraries (assuming they don't already provide promises) with a promise library like Q . 避免这种情况的最好方法是使用像Q这样的promise库来包装异步库(假设它们还没有提供promises)。 IMO, promises provide a much more clear picture of the execution path. IMO,承诺提供了更加清晰的执行路径图。

You can avoid the predicament of not knowing where variables came from by binding parameters to your named function using bind , for instance: 通过使用bind参数绑定到命名函数,可以避免不知道变量来自何处的困境,例如:

function handleRequest(res, err, text, url) {
    if (err) {
        res.status(HttpStatus.INTERNAL_SERVER_ERROR).send(err);
        return;
    }
    var newMongooseModel = new AnotherMongooseModel();
    newMongooseModel.id = id;

    newMongooseModel.save(function (err) {
        if (err) {
            res.status(HttpStatus.INTERNAL_SERVER_ERROR).send(err);
        } else {
            res.send({url: url, text: text});
        }
    });
}

...
generateUrl(Id, handleRequest.bind(null, res));

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM