Node.JS - How to escape multiple nested callbacks?

Viewed 759

I've currently got a expressJS app and I'm trying to retrieve information from an API using the getStats function. However, the profile route I've written seems to be multiple nested callbacks. How would I prevent this? Is there a way to make it get all of the stats, then assign them to variables after they all of the stats are retrieved?

function getStats(access_token, time_range, x, callback) {
    var stats = [];

    var options = {
        url: 'www.example.com',
        headers: {
            'Accept': 'application/json',
            'Content-Type': 'application/json'
        },
        json: true
    }

    request(options, function(error, response, body) {
        if (!error && response.statusCode == 200) {
            for (i = 0; i < body.items.length; i++) {
                stats.push(body.items[i].name);
            }
            return callback(stats);
        }
    })

}

app.get('/profile', function(req, res) {
    var access_token = 1234;

    getStats(access_token, 's', 'x', function(a){
        console.log(a);

        getStats(access_token, 's', 'y', function(b){
            console.log(b);

            getStats(access_token, 'm', 'x', function(c){
                console.log(c);

                getStats(access_token, 'm', 'y', function(d){
                    console.log(d);

                    getStats(access_token, 'l', 'x', function(e){
                        console.log(e);

                        getStats(access_token, 'l', 'y', function(f){
                            console.log(f);

                            res.send(a + "\n" + b + "\n" + c + "\n" + d + "\n" + e + "\n" + f);
                        });
                    });
                });
            });
        });
    });
});
3 Answers

Promises allow you to avoid the type of callback nesting like you've just shown. See my example which illustrates your example in Promise form:

function getStats(access_token, time_range, x, prevResult) {
    return new Promise((resolve, reject) => {
        if (prevResult) {
            resolve([...prevResult, "test", "test", "test"]);
        } 
        return resolve(["test", "test", "test"]);

    });
}

app.get('/profile', (req, res) => {
    var access_token = 1234;

    getStats(access_token, 's', 'x')
        .then((a) => {
            console.log(a);
            return getStats(access_token, 's', 'y', a);
        })
        .then((b) => {
            console.log(b);
            return getStats(access_token, 'm', 'x', b);
        })
        .then((c) => {
            console.log(c);
            return getStats(access_token, 'm', 'y', c);
        })
        .then((d) => {
            console.log(d);
            return getStats(access_token, 'l', 'x', d);
        })
        .then((e) => {
            console.log(e);
            return getStats(access_token, 'l', 'y', e);
        })
        .then((f) => {
            console.log(f);
            res.send(f.join("\n"));
    });
});

As you can see the nested callback structure and replace with a much more readable format. Read more about promises here.

The above could even be rewritten using Promise.all, which looks even nicer:

function getStats(access_token, time_range, x) {
    return new Promise((resolve, reject) => {
        return resolve(["test", "test", "test"]);
    });
}

app.get('/profile', (req, res) => {
    var access_token = 1234;

    Promise.all([
        getStats(access_token, 's', 'x'),
        getStats(access_token, 's', 'y'),
        getStats(access_token, 'm', 'x'),
        getStats(access_token, 'm', 'y'),
        getStats(access_token, 'l', 'x'),
        getStats(access_token, 'l', 'y')
    ]).then((values) => {
        console.log(values);
        res.send(values.join("\n"));
    });
});

Promises will help with that, or moving your callbacks into their own function expressions outside of the route, and then calling it in the route once will help.

The problem with that callback hell is that you are not using middlewares where the express app is supporting it.

Basically a way to write an express app is relying in the callbacks and so on. But the counterpart is that this will lead to as what you pointed out the callback hell.

This happens also with promises as well and it's called the promise hell when not properly handled.

The best way to avoid this two patterns are moving into the middlewares solutions.

function getAccesstoken(req, res, next) {
    if (!req.something) {
        return next('Error, something was not specified');
    }
    req.someRequestVariable = 'set_something';
    next();
}

function processRequest(req, res, next) {
    console.log(req.someRequestVariable);
    res.send(200);
}

app.use(function genericHandlerForEveryCall() {
    console.log('This is being called in every request');
})

app.get('/profile', getAccesstoken, processRequest);
Related