Node.JS + Mongoose 回调地狱
Posted
技术标签:
【中文标题】Node.JS + Mongoose 回调地狱【英文标题】:Node.JS + Mongoose callback hell 【发布时间】:2017-04-06 13:23:01 【问题描述】:如何将 mongoose 保存到 db 但先等待其他集合加载? 平台和流派是空的,因为“保存”功能在平台和流派加载之前运行,请帮助!
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);
);
【问题讨论】:
【参考方案1】:好的,首先,如果你不使用回调,猫鼬(至少任何最新版本)已经支持承诺......其次,下面的示例使用结合异步功能的承诺。这在 Node 7+ 中的选项标志后面,因此您应该使用 babel 进行转译。
我把 cmets 放在你应该优化你的 mongodb 调用的地方,但让逻辑尽可能接近上面,希望这对你有帮助。
关键点是......
使用 Promise,不要害怕创建额外的函数来分解逻辑 Promise.all 可用于等待并行操作完成async
功能很棒。
代码:
// 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】:您可以使用异步模块来完成这项工作,它非常适合执行此类任务。使用 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
【讨论】:
【参考方案3】:这是一个不错的 Promise 用例(它是一个出色的工具,使您能够轻松地执行异步操作),将来应该会对您有所帮助。
当前代码的问题是findOne
操作是异步的,并且会在一段时间后完成。同时,下一行将开始执行。因此,当您到达save
状态时,findOne
都不会完成,您会得到空数组
实现 Promise 的两个流行的 nodejs 库是 Q 和 Bluebird。最新版本的 NodeJS 也实现了默认的Promise
以下是使用 Bluebird 的代码。您基本上必须为涉及平台和流派中的findOne
的每个数据库操作创建承诺。当所有这些都完成后,您必须开始执行最后的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 已经返回了 Promise。 感谢您指出这一点。只是想为来自同步背景的人解释如何使用 Promise。 @hyades,很好,但是如果你有三个数组要得到,你只需在“结果”中混合 platformPromises 和genrePromises,如果有三个数组,“for”函数会是什么样子?例如,platformPromises、genrePromises、themePromises @tonywei 是的,结果数组将与输入数组的顺序相同,以上是关于Node.JS + Mongoose 回调地狱的主要内容,如果未能解决你的问题,请参考以下文章
Node.js回调地狱及使用Promiseasync和await函数的解决方法
Mongoose:我如何避免回调地狱,同时允许对不返回承诺的 mongoose 方法进行存根?
Node.js Promise对象(解决回调地狱问题)async和await函数