简体   繁体   English

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

[英]rendering results of multiple DB/mongoose queries to a view in express.js

given the async nature of mongoose (or sequelize, or redis) queries, what do you do when you have multiple queries you need to make before rendering the view? 鉴于mongoose(或sequelize,或redis)查询的异步性质,当你在渲染视图之前需要进行多次查询时,你会怎么做?

For instance, you have a user_id in a session, and want to retrieve some info about that particular user via findOne . 例如,您在会话中有user_id ,并希望通过findOne检索有关该特定用户的一些信息。 But you also want to display a list of recently logged in users. 但您还希望显示最近登录用户的列表。

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

So res.render is in the first query callback, fine. 所以res.render在第一个查询回调中,很好。 But what about waiting on the response from findOne to see if we know this user? 但是等待来自findOne的响应,看看我们是否知道这个用户呢? It is only called conditionally, so I can't put render inside the inner callback, unless I duplicate it for either condition. 它只是有条件地调用,所以我不能将render放在内部回调中,除非我为任何一个条件复制它。 Not pretty. 不漂亮。

I can think of some workarounds - 我可以想到一些解决方法 -

  • make it really async and use AJAX on the client side to get the current user's profile. 使它真的异步并在客户端使用AJAX来获取当前用户的个人资料。 But this seems like more work than it's worth. 但这似乎比它的价值更多的工作。

  • use Q and promises to wait on the resolution of the findOne query before rendering. 使用Q和promises在渲染之前等待findOne查询的解析。 But in a way, this would be like forcing blocking to make the response wait on my operation. 但在某种程度上,这就像强制阻止使响应等待我的操作。 Doesn't seem right. 似乎不对。

  • use a middleware function to get the current user info. 使用中间件功能获取当前用户信息。 This seems cleaner, makes the query reusable. 这看起来更干净,使查询可重用。 However I'm not sure how to go about it or if it would still manifest the same problem. 但是,我不确定如何去做,或者它是否仍会出现同样的问题。

Of course, in a more extreme case, if you have a dozen queries to make, things might get ugly. 当然,在一个更极端的情况下,如果你有十几个查询要做,事情可能会变得丑陋。 So, what is the usual pattern given this type of requirement? 那么,给定这种要求的通常模式是什么?

Yep, this is a particularly annoying case in async code. 是的,这是异步代码中特别烦人的情况。 What you can do is to put the code you'd have to duplicate into a local function to keep it DRY: 你可以做的是把你必须复制的代码放到一个本地函数中,以保持干燥:

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

However, looking at what you're doing here, you'll probably need to look up the current player information in multiple request handlers, so in that case you're better off using middleware. 但是,看看你在这里做了什么,你可能需要在多个请求处理程序中查找当前的播放器信息,所以在这种情况下你最好使用中间件。

Something like: 就像是:

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

Then you'd configure your routes to call loadUser wherever you need req.player populated and the route handler can just pull the player details right from there. 然后你可以配置你的路由来调用loadUser只要你需要req.player填充,路由处理程序就可以直接从那里拉出玩家详细信息。

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

Nowadays you can use app.param in ExpressJS to easily establish middleware that loads needed data based on the name of parameters in the request URL. 现在,您可以使用ExpressJS中的app.param轻松建立中间件,根据请求URL中的参数名称加载所需的数据。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM