Node.JS + Mongoose回调地狱

时间:2016-11-22 18:30:17

标签: node.js mongodb asynchronous mongoose

如何将mongoose保存到db但是等待其他集合首先加载? 平台和流派是空的,因为" save"功能在平台和流派加载之前运行,请帮助!

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);
    }

    });
}

3 个答案:

答案 0 :(得分:3)

这是promises的一个不错的用例之一(这是一个很好的工具,使您能够轻松地执行异步操作),并将在未来帮助您。

当前代码的问题是findOne操作是异步的,并且会在一段时间后完成。与此同时,下一行将开始执行。因此,当您达到save状态时,findOne都不会完成,并且您将获得空数组

实现承诺的两个流行的nodejs库是QBluebird。最新版本的NodeJS还实现了默认的Promise

以下是使用Bluebird的代码。您必须为Platform和Genre中涉及findOne的每个数据库操作创建promise。完成所有这些后,您必须开始执行最终的save部分。这是使用Promise.all功能实现的,该功能将等待所有承诺完成。

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);
        }

    });

})

答案 1 :(得分:2)

好的,首先,mongoose(至少是任何最新版本)已经支持promises如果你不用回调...第二,下面的例子使用promises和异步函数。这是Node 7+中的选项标志的背后,所以你应该使用babel进行转换。

我把评论放在你应该优化你的mongodb调用的地方,但是让逻辑尽可能接近上面,希望这对你有帮助。

关键的收获是......

  • 使用Promise,不要害怕创建其他功能来分解逻辑
  • Promise.all可用于等待并行操作完成
  • async功能非常棒。

CODE:

// will asynchronously map your release date elements to the Platform
async function getPlatforms(releaseDates) {
  // TODO: change to single query with only needed properties
  return await Promise.all(releaseDates.map(
    elem => Platform.findOne({ id: elem.platform })
  ));
}

// will asynchronously map your genre list into the appropriate ObjectId objects
async function getGenres(genres) {
  // TODO: change to return only single properties in a single query
  var genres = await Promise.all(genres.map(elem => Genre.findOne({ id: elem })));
  return genres.map(result => mongoose.Types.ObjectId(result._id));
}

// asynchronous request handler (ALWAYS use a try/catch for this with express)
// not sure if current/future versions will allow for promise resulting 
// handlers/errors
async function saveGameDetails(req,res) {
  try {
    // array destructured assignment, decomposes the array
    // await will await the promise, and promise.all will take an array
    // and wrap them into a single promise.
    var [platforms, genres] = await Promise.all([
      getPlatforms(body.release_dates),
      getGenres(body.genres)
    ]);

    //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);
    await data.save(); //already a promise, just wait for it

    // return normal result
    res.status(200).json({ success: true });
  } catch(err) {
    // generic error handler, may want to have this even more generic via express
    res.status(500).json({
      error: {
        message: err.message || 'Unknown Server Error';
      }
    })
  }
}

答案 2 :(得分:1)

可以用来完成这项工作的是异步模块,它非常适合执行这类任务。 Intall使用npm:npm i -S async

    var async = require ('async');

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

        async.parallel([
        function(cb){
            body.release_dates.forEach(function(elem){
                Platform.findOne({ id : elem.platform}, function(err, result){
                    cb(null,mongoose.Types.ObjectId(result._id))
                });
            });
         },
        function(cb){
            body.genres.forEach(function(elem){
                Genre.findOne({id: elem},enter code here function(err, result){
                    cb(null,mongoose.Types.ObjectId(result._id));
                })
            });
         }],function(err,results){
            //here you'll get an array of results ordered by your tasks
                if(!err){
                    platforms.push(results[0])
                    genres.push(results[1])
                }
             })

我没有运行此代码,但就是这样,如果您需要更多信息,可以阅读文档:http://caolan.github.io/async/docs.html

相关问题