bicycle
bicycle

Reputation: 8438

Integrating asynchronous mongo call within an inner forEach loop

I got two loops, the outer loops over the users and the inner one loops over the venueID's of each user. Within the inner loop I want to look up the venue and attach it to an array defined in the outer look (userItem). However because forEach is synchronous and the mongo database look up is asynchronous the result always remains empty. I've tried to integrate this answer but to no avail. How to do this?

ret = [];
users.forEach(function(user) {
    var userItem = user.getSanitised('ADM');

    userItem.venues = [];

    var tmp = [];

    userItem.adminVenueIds.forEach(function(adminVenueId){
        tmp.push(function(callback) {

            Venue.findOne({_id:adminVenueId}, function(error, venue) {

                callback(null, venue.toObject());
            });
        });

    });

    async.parallel(userItem.venues, function(err, result) {
        /* this code will run after all calls finished the job or
         when any of the calls passes an error */
        if (err)
            return console.log(err);

        userItem.venues.push(result);
    });
    ret.push(userItem);
});

Tried the following as well but doesn't work also

users.forEach(function(user) {

    var userItem = [];
    async.series({

            setUserItem : function(callback)
            {
                userItem = user.getSanitised('ADM');
                callback(null, 'OK');
            },
            setUserVenues : function(callback)
            {
                userItem.venues = [];
                user.adminVenueIds.forEach(function(adminVenueId,index) {

                    Venue.findOne({_id:adminVenueId}, function(error, venue) {

                        userItem.venues.push(venue.toObject());

                        if((index+1) == user.adminVenueIds.length)
                            callback(null, 'OK');

                    });
                });
            }

        },
        function(error, results) {

            if(error)
                winston.error(error);

            ret.push(userItem);

        }

    );


});

Upvotes: 1

Views: 623

Answers (3)

bicycle
bicycle

Reputation: 8438

I ended up with the following solution. It's dirty but I guess that's just nodejs being nodejs.

users.forEach(function(user) {
    var userItem = user.getSanitised('ADM');
    userItem.venues = [];


    user.adminVenueIds.forEach(function(adminVenueId) {

        Venue.findOne({_id:adminVenueId}, function(error, venue) {

            userItem.venues.push(venue.toObject());

        });
    });

    (function(){

        if(userItem.venues.length == user.adminVenueIds.length) {

            ret.push(userItem);

        } else {
            setTimeout(arguments.callee, 30);
        }
    })();
});

Upvotes: 0

Jeffrey A. Gochin
Jeffrey A. Gochin

Reputation: 964

I think you might want to look into using Mongoose. It is a NodeJS application layer on top of MongoDB that provides a more SQL like experience.

http://mongoosejs.com

Upvotes: 0

Datsik
Datsik

Reputation: 14824

You could simply put an if statement (in your case put the conditional as the array length) then when the loop is done you could then make it continue doing its thing by calling a function to continue (or put your code in there, but it will start to look messy)

var ret = [];
var test = [];


for (var i = 0; i < 20; i++) {
    for (var x = 0; x < 20; x++) {
         setTimeout(function() {
            test.push("Test"+x);
            if (x === 20) {
                finishIt();
            }
        }, 300)
    }
}


function finishIt() {
    console.log(test);
    ret.push(test);
}

Upvotes: 1

Related Questions