Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Node.JS + Mongoose callback hell

how can i save mongoose to db but wait other collection to load first? The platform and genres is empty, because the "save" function run before platform and genres loaded, please help!

var platforms = []; //load platforms
body.release_dates.forEach(function(elem){
    Platform.findOne({ id : elem.platform}, function(err, result) {
            platforms.push(mongoose.Types.ObjectId(result._id));
        });
});

var genres = []; //load genre
body.genres.forEach(function(elem){
    Genre.findOne({id: elem}, function(err, result){
        genres.push(mongoose.Types.ObjectId(result._id));
    })
});



//prepare to save!
var game = {
    igdb_id : body.id,
    name : body.name,
    summary : body.summary,
    storyline : body.description,
    genres : genres,
    platforms : platforms, // <- genres amd platforms empty and not wait platforms and genre array to complete
    release_date : body.original_release_date,
    cover : body.cover.cloudinary_id,
    videos: body.videos
};

var data = new Game(game);
data.save(function(err, game){
    if(err){
        res.send("500");
        return console.error(err);
    }

    });
}
like image 225
tonywei Avatar asked Nov 20 '25 08:11

tonywei


1 Answers

This is one of a decent use case for promises (which are an excellent tool enabling you to perform async operations with ease) and should help you in future.

The issue with the current code is that the findOne operations are async and would complete after some time. In the meantime, the next lines will start executing. Thus, when you reach at the save state, none of the findOne would have completed and you get empty arrays

Two popular nodejs libraries which implement promises are Q and Bluebird. Latest versions of NodeJS also implement the default Promise

The following is the code using Bluebird. You essentially have to create promises for each of your database operation involving findOne in Platform and Genre. When all of these complete, you have to start executing the final save part. This is achieved using the Promise.all functionality which would wait till all the promises are completed.

var Promise = require('bluebird')

var platformPromises = []; //load platforms
body.release_dates.forEach(function(elem){
    platformPromises.push(new Promise(function (resolve, reject) {
        Platform.findOne({ id : elem.platform}, function(err, result) {
            if(err) 
                reject(err);
            else
                resolve(mongoose.Types.ObjectId(result._id));
        });
    }))

});

var genrePromises = []; //load genre
body.genres.forEach(function(elem){
    genrePromises.push(new Promise(function (resolve, reject) {
        Genre.findOne({id: elem}, function(err, result){
            if(err) 
                reject(err);
            else
                resolve(mongoose.Types.ObjectId(result._id));
        });
    }))

});

var allPromises = platformPromises.concat(genrePromises);

Promise.all(allPromises).then(function (result) {
    //prepare to save!

    var platforms = [];
    var genres = [];

    for(var i=0; i<platformPromises.length; i++)
        platforms.push(result[i]); // result come out in same order as the promises

    for(var i=platformPromises.length; i<result.length; i++)
        genres.push(result[i]);

    var game = {
        igdb_id : body.id,
        name : body.name,
        summary : body.summary,
        storyline : body.description,
        genres : genres,
        platforms : platforms,
        release_date : body.original_release_date,
        cover : body.cover.cloudinary_id,
        videos: body.videos
    };

    var data = new Game(game);
    data.save(function(err, game){
        if(err){
            res.send("500");
            return console.error(err);
        }

    });

})
like image 160
hyades Avatar answered Nov 23 '25 01:11

hyades



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!