5

Basically, I am trying to call a function, where a loop is running inside of which many callback function are present (callback hell).. as below:

for(var i=0;i<data.id.length;i++)
{
    DAO.getUserById(data.id[i],function(err,resp1)
    {
       /* some other work based on resp1 */
       DAO.getOtherData(resp1.username,resp1.userId,function(err,resp2)
       {
           /* similary some other work*/
       });
    });
}  

I have same pattern at several location in my app, and some time I faced issue with the callback, that for loop get over, but callback do not give response, that seems like DAO method has been called up but still waiting for response. Is there any optimized way, to overcome this issue ?

It would be nice to know if there is some javascript coding pattern available to overcome this issue.(other than any 3rd party library) Thanks

Aman Gupta
  • 5,548
  • 10
  • 52
  • 88

3 Answers3

3

Have you heard about Deferreds or Promises? I think that's what you're looking for. In it's basic form it's basically a object which has two handler. One for failure and one for success.

But there are other helper functions like then or when which let you chain the functions in a more readable way. Have look at q or the jQuery implementation. For a very good introduction read the book Async JavaScript.

Edit:/

I did a little working example as js fiddle for you.

    var data = { id : [] };

    for(var i = 0; i < 10; i++) {
        data.id.push(i);
    }

    // DAO definition
    var DAO = {
        getUserById : function(id) {
            var deferred = $.Deferred();

            setTimeout(function() { 
                var isError = Math.floor(Math.random()*11) > 5;

                if(isError) {
                    deferred.reject("WOW - Much Handler - So error");
                } else {
                    deferred.resolve({
                        username :  'Max',
                        userId : id
                    }); 
                }
            }, 50);

            return deferred.promise();
        },

        getOtherData : function(username, userId) {
            var deferred = $.Deferred();

            setTimeout(function() {
                deferred.resolve((username + ' id: ' + userId));
            }, 20);

            return deferred.promise();
        }
    };



    function printResult(res) {
        $('#result').html($('#result').html() + '<br />' + res);
    };

    // DAO usage

    for(var i=0;i<data.id.length;i++)
    {
        DAO.getUserById(data.id[i])
        .done(function(res) {
            DAO.getOtherData(res.username, res.userId).done(function(result) {
                printResult(result);
            });
        })
        .fail(function(res) {
            printResult(res);
        });
    }

The great advantage of that is twofold:

  1. You get seperation of error handler code and result handler code for free
  2. It prevents you from nesting hell. (callbacks, in callbacks, in callbacks ...) Due to the deferreds you're able to factore the actual logic out.
  3. Synchronizing of callbacks gets very easy because you only need to use when.

I used jQuerys Deferreds because jsFiddle has jquery in a dropdown box. You could use any implementation you want.

When you got the concept implementing it yourself shouldn't be too hard.

schlingel
  • 8,560
  • 7
  • 34
  • 62
  • *Deferreds or Promises* I heard about them but I could not make it possible to implement with my code. is there any good resource/ebook available using which I can resolve my issue ? – Aman Gupta Nov 21 '13 at 09:32
  • @Aman The book Async JavaScript. It also talks about waterfall mentioned by Paul. Yous hould rewrite your DAO functions to actual return promises and attach your handler to them. Then you can avoid this nasty nesting. – schlingel Nov 21 '13 at 09:41
  • I found it little tedious (beginner in nodejs) sorry about that, but would you plz share some code as an example to use Deferreds or Promises ? It would a nice thing, basically I do not want to go for any 3rd party library, want to implement it self written way.. plz. thanks – Aman Gupta Nov 28 '13 at 04:51
  • @Aman - See working example. If you don't want to use a finished product write it yourself. Basic done/fail handling isn't too hard to write it yourself. – schlingel Nov 28 '13 at 11:30
2

You can use the async.waterfall function that is exactly meant to deal with your issue. All functions are called in series, with the result of a function sent as a parameter to the next function. Here is a sample usage, from the documentation:

async.waterfall([
    function(callback){
        callback(null, 'one', 'two');
    },
    function(arg1, arg2, callback){
        callback(null, 'three');
    },
    function(arg1, callback){
        // arg1 now equals 'three'
        callback(null, 'done');
    }
], function (err, result) {
   // result now equals 'done'    
});
Paul Mougel
  • 16,728
  • 6
  • 57
  • 64
0

Take a look at this code.

var i=0;
var length = data.id.length;
var getData = function(index)
{
    DAO.getUserById(data.id[index],function(err,resp1)
    {
        /* some other work based on resp1 */
        DAO.getOtherData(resp1.username,resp1.userId,function(err,resp2)
        {
             /* similary some other work*/
             index++;
             if(index < length)
             {
                getData(index);
             }
        });
   });
}
getData(i);

is this what u need?

Gowsikan
  • 5,571
  • 8
  • 33
  • 43
  • no no... I have already implemented this, basically I want to know why the callback hangs ? and how to avoid nested callbacks. – Aman Gupta Nov 21 '13 at 09:30
  • @Gowsikan, how would you know if `getData(i)` is completed and returned a value ? thing is some time callback do not give you output immediately based on the task assigned or priority to nodejs thread (if I am not wrong). – Aman Gupta Nov 21 '13 at 09:59
  • @Aman Return a promise and resolve the deferred when you reach the else branch in the inner loop. – schlingel Nov 21 '13 at 10:02
  • this method is just to call next method only after get the response of previous(may be a success or fail). If u put a for loop the loop will end before u get the response. – Gowsikan Nov 21 '13 at 10:05