Node.JS + Mongoosecallback地狱

我怎样才能将mongoose保存到数据库,但等待其他收集首先加载? 平台和stream派是空的,因为“保存”function在平台和stream派加载之前运行,请帮助!

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

这是一个体面的承诺用例之一(这是一个很好的工具,使您可以轻松地执行asynchronous操作),并将在未来帮助您。

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

Q和Bluebird是实现promise的两个stream行的nodejs库。 NodeJS的最新版本也实现了默认的Promise

以下是使用蓝鸟的代码。 你必须为平台和stream派中涉及findOne每个数据库操作创build承诺。 当所有这些完成后,您必须开始执行最后的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); } }); }) 

好的,首先,mongoose(至less任何最新版本)已经支持承诺,如果你离开callback…第二,下面的例子使用承诺结合asynchronousfunction。 这是Node 7+中的一个选项标志的后面,所以你应该使用babel来转发。

我把评论放在哪里,你应该优化你的mongodb调用,但离开尽可能接近逻辑的逻辑,希望这可以帮助你。

关键是拿走…

  • 使用Promise,不要害怕创build额外的function来分解逻辑
  • Promise.all可用于等待并行操作完成
  • asyncfunction真棒。

码:

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

你可以用来做这个工作的是asynchronous模块,这是完成这种任务的完美。 安装使用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