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

时间:2012-11-06 02:34:09

标签: node.js express mongoose

鉴于mongoose(或sequelize或redis)查询的异步特性,当你在渲染视图之前需要进行多次查询时,你会怎么做?

例如,您在会话中有user_id,并希望通过findOne检索有关该特定用户的一些信息。但您还想显示最近登录用户的列表。

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在第一个查询回调中,很好。但是等待来自findOne的回复看看我们是否认识这个用户呢?它只是有条件地调用,所以我不能将render放在内部回调中,除非我为任何一个条件复制它。不漂亮。

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

  • 使真正异步,并在客户端使用AJAX获取当前用户的个人资料。但这似乎比它的价值更多。

  • 使用Q并承诺在呈现之前等待findOne查询的解析。但在某种程度上,这就像强制阻止使响应等待我的操作。似乎不对。

  • 使用中间件功能获取当前用户信息。这看起来更干净,使查询可重用。但是我不确定如何解决它,或者它是否仍会出现同样的问题。

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

3 个答案:

答案 0 :(得分:5)

是的,这是异步代码中特别烦人的情况。您可以做的是将您必须复制的代码放入本地函数中以使其保持干燥状态:

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

然后您将路由配置为在loadUser填充req.player时调用{{1}},路由处理程序可以直接从那里提取玩家详细信息。

答案 1 :(得分:1)

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

答案 2 :(得分:0)

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

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