将多个DB / mongoose查询的结果呈现给express.js中的视图

考虑到mongoose(或者后缀或者redis)查询的asynchronous性质,当你在渲染视图之前需要做多个查询时,你会做什么?

例如,在会话中有一个user_id ,并且想通过findOne检索关于该特定用户的一些信息。 但是你也想显示最近login的用户列表。

 exports.index = function (req, res) { var current_user = null Player.find({last_logged_in : today()}).exec(function(err, players) { if (err) return res.render('500'); if (req.session.user_id) { Player.findOne({_id : req.session.user_id}).exec(function(err, player) { if (err) return; if (player) { current_user = player } }) } // here, current_user isn't populated until the callback fires res.render('game/index', { title: 'Battle!', players: players, game_is_full: (players.length >= 6), current_user: current_user }); }); }; 

所以res.render是在第一个查询callback,罚款。 但是等待findOne的响应,看看我们是否知道这个用户呢? 它只是有条件地调用,所以我不能把render内部callback,除非我复制它的条件。 不漂亮。

我可以想到一些解决方法 –

  • 使其真正asynchronous,并在客户端使用AJAX获取当前用户的configuration文件。 但是,这似乎是更多的工作,而不是价值。

  • 使用Q并承诺在呈现之前等待findOne查询的分辨率。 但是从某种意义上说,这就像是强制阻止让我的操作等待响应一样。 看起来不正确。

  • 使用中间件function获取当前用户信息。 这似乎更清洁,使查询可重用。 但是,我不知道该怎么做,或者如果它仍然performance出同样的问题。

当然,在更极端的情况下,如果你有十几个查询要做,事情可能会变得很难看。 那么,在这种types的要求下,通常的模式是什么?

是的,在asynchronous代码中这是一个特别恼人的情况。 你可以做的是把你必须复制到一个本地函数的代码,以保持干燥:

 exports.index = function (req, res) { var current_user = null Player.find({last_logged_in : today()}).exec(function(err, players) { if (err) return res.render('500'); function render() { res.render('game/index', { title: 'Battle!', players: players, game_is_full: (players.length >= 6), current_user: current_user }); } if (req.session.user_id) { Player.findOne({_id : req.session.user_id}).exec(function(err, player) { if (err) return; if (player) { current_user = player } render(); }) } else { render(); } }); }; 

然而,看看你在做什么,你可能需要在多个请求处理程序中查找当前的玩家信息,所以在这种情况下,最好使用中间件。

就像是:

 exports.loadUser = function (req, res, next) { if (req.session.user_id) { Player.findOne({_id : req.session.user_id}).exec(function(err, player) { if (err) return; if (player) { req.player = player } next(); }) } else { next(); } } 

然后你可以configuration你的路由,以便在需要req.player填充的地方调用loadUser ,并且路由处理器可以从那里拉取玩家的详细信息。

 router.get("/",function(req,res){ var locals = {}; var userId = req.params.userId; async.parallel([ //Load user Data function(callback) { mongoOp.User.find({},function(err,user){ if (err) return callback(err); locals.user = user; callback(); }); }, //Load posts Data function(callback) { mongoOp.Post.find({},function(err,posts){ if (err) return callback(err); locals.posts = posts; callback(); }); } ], function(err) { //This function gets called after the two tasks have called their "task callbacks" if (err) return next(err); //If an error occurred, we let express handle it by calling the `next` function //Here `locals` will be an object with `user` and `posts` keys //Example: `locals = {user: ..., posts: [...]}` res.render('index.ejs', {userdata: locals.user,postdata: locals.posts}) }); 

现在,您可以使用ExpressJS中的app.param轻松build立根据请求URL中参数名称加载所需数据的中间件。

http://expressjs.com/4x/api.html#app.param