Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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. 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?

like image 571
Nepoxx Avatar asked Sep 19 '14 19:09

Nepoxx


People also ask

How do you prevent callback hell in Nodejs?

We can avoid the callback hell with the help of Promises. Promises in javascript are a way to handle asynchronous operations in Node. js. It allows us to return a value from an asynchronous function like synchronous functions.

How do I stop nested callbacks?

Use ES2017 async functions and await to write flat asynchronous code instead of infinite nesting callbacks. You can use await natively or with the help of TypeScript or Babel.

How are promises more superior than callbacks How do promises solve the issue of callback hell?

They can handle multiple asynchronous operations easily and provide better error handling than callbacks and events. In other words also, we may say that, promises are the ideal choice for handling multiple callbacks at the same time, thus avoiding the undesired callback hell situation.

Why is async await better than callbacks?

Async functions not only allow the programmer to escape from callback hell and promise chaining in asynchronous code, but they also make the code seemingly synchronous. Ace your System Design Interview and take your career to the next level.


1 Answers

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?

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"). In my own code, I use the naming convention .._maker or generate_.. to denote that I'm calling a function factory. 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). 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.

like image 129
slebetman Avatar answered Sep 30 '22 18:09

slebetman