NodeJS不太可见的回调和更多的结构

时间:2012-10-20 17:49:40

标签: node.js postgresql asynchronous express

NodeJS的一个优点是它的异步和非阻塞I / O,一方面在我的情况下非常好,但另一方面每天都会让我受伤。

我认为自己是NodeJS / Async新手,我经常最终得到这样的代码:

function(req, res) {
            req.assert("name", "Lobbyname is required").notEmpty();
            req.assert("name", "Lobbyname length should be between 4 and 64 characters").len(4, 64);
            req.assert("game", "Game not found").isInt();

            req.sanitize("game").toInt();
            var userId = req.user.id;

            var errors = req.validationErrors();
            var pg_errors = [];
            var games = null;
            if (errors) {
                console.log(errors);
                client.query("SELECT * FROM games", function(err, result) {
                    if (!err) {
                        games = result.rows;
                        res.render("lobby/create", {
                            title: "Create a new lobby",
                            games: games,
                            errors: errors.toString()
                        });
                    }
                    else {
                        res.send("error");
                    }
                });
            }
            else {
                errors = null;
                client.query("SELECT COUNT(*) as in_lobbies FROM users u RIGHT JOIN lobby_userlist ul ON ul.user_id = u.id WHERE u.id = $1", [userId], function(err, result) {
                    if (!err) {
                        console.log(result.rows[0]);
                        if (result.rows[0].in_lobbies < 1) {
                            client.query("SELECT COUNT(*) as hosting_lobbies FROM lobbies WHERE owner = $1", [userId], function(err, result) {
                                if (!err) {
                                    if (result.rows[0].hosting_lobbies < 1) {
                                        client.query("INSERT INTO lobbies(name, game, owner) VALUES($1, $2, $3)", [req.param("name"), req.param("game"), userId], function(err, result) {
                                            if (!err) {
                                                res.redirect("/lobby");
                                            }
                                            else {
                                                pg_errors.push(err);
                                                console.log(err);
                                            }
                                        });
                                    }
                                    else {
                                        errors = "You can only host one lobby at a time";
                                    }
                                }
                                else {
                                    pg_errors.push(err);
                                    client.query("SELECT * FROM games", function(err, result) {
                                        if (!err) {
                                            games = result.rows;

                                            res.render("lobby/create", {
                                                title: "Create a new lobby",
                                                games: games,
                                                errors: errors
                                            });
                                        }
                                        else {
                                            pg_errors.push(err);
                                        }
                                    });
                                }
                            });
                        }
                        else {
                            pg_errors.push(err);
                        }
                    }
                });

                console.log("pg_errors");
                console.log(pg_errors);
                console.log("pg_errors _end");

                if (pg_errors.length < 1) {
                    console.log("no errors");
                }
                else {
                    console.log(pg_errors);
                    res.send("error service operation failed");
                }
            }
        }

这是我用以下npm包编写的一个例子:

  • pg(native)
  • 表示
  • express-validator(节点验证器的中间件)
  • passport(auth middleware)

检查用户给出的输入是否有效是最不重要的问题,我检查了断言变量并将页面的渲染版本打印出来并将错误打印给用户。

但是如果我们首先传递验证错误,我们假设“lobby”已准备好插入数据库,之后我想确保用户没有其他大厅打开且不是另一个大厅的成员。 那么现在我最终将一个查询放到另一个查询中,理论上如果查询遇到错误或返回一个结果表明不允许用户,我必须将我的视图渲染函数(res.render())放入每个查询回调中创建一个大厅。 我不希望这样,似乎不太可行。

我尝试从查询回调中删除渲染逻辑和所有其他逻辑,而是让查询回调设置错误数组或变量,这将指示成功或失败,在我的查询代码下面,我将检查(错误)renderPageWithErrors。

这会导致由于nodejs的异步行为导致的奇怪错误,在这种情况下res.redirect()在res.render()之后被调用。 我不得不将res.render移回查询回调中。

有没有正确的方法呢?

2 个答案:

答案 0 :(得分:1)

您可能希望查看async库,例如https://github.com/caolan/async。它有助于构建异步代码,因此它不会变成这样的混乱。根据您的要求,有不同的方法,从简单的seriesparallel执行到waterfallauto等执行依赖关系跟踪的操作。

async.auto({
    get_data: function(callback){
        // async code to get some data
    },
    make_folder: function(callback){
        // async code to create a directory to store a file in
        // this is run at the same time as getting the data
    },
    write_file: ['get_data', 'make_folder', function(callback){
        // once there is some data and the directory exists,
        // write the data to a file in the directory
        callback(null, filename);
    }],
    email_link: ['write_file', function(callback, results){
        // once the file is written let's email a link to it...
        // results.write_file contains the filename returned by write_file.
    }]
}, function(err) { 
    // everything is done or an error occurred 
});

它做的另一件好事是将所有错误合并到一个回调中。这样,您只需在一个地方处理错误,而不是在整个代码中散布错误。

答案 1 :(得分:1)

您可能还想检查https://github.com/0ctave/node-sync库。它是nodejs Fibers的语法糖,这是一种以传统方式编写异步代码而不破坏nodejs事件循环模型的方法。关于使用Fibers的优缺点有很多讨论,但我更喜欢代码可读性和易于开发而不是潜在的小资源使用增加。

我不知道你的所有代码逻辑,但上面的函数看起来像这样:

function(req, res) {
    Sync(function() {
        req.assert("name", "Lobbyname is required").notEmpty();
        req.assert("name", "Lobbyname length should be between 4 and 64 characters").len(4, 64);
        req.assert("game", "Game not found").isInt();

        req.sanitize("game").toInt();
        var userId = req.user.id;

        var errors = req.validationErrors();
        var pg_errors = [];
        var games = null;
        if (errors) {
            console.log(errors);
            var games = client.query.sync(client, "SELECT * FROM games").rows;
            games = result;
            res.render("lobby/create", {
                title: "Create a new lobby",
                games: games,
                errors: errors.toString()
            });
        }
        else {
            errors = null;
            var result = client.query.sync(client, "SELECT COUNT(*) as in_lobbies FROM users u RIGHT JOIN lobby_userlist ul ON ul.user_id = u.id WHERE u.id = $1", [userId]);

            console.log(result.rows[0]);
            if (result.rows[0].in_lobbies < 1) {
                var result = client.query.sync(client, "SELECT COUNT(*) as hosting_lobbies FROM lobbies WHERE owner = $1", [userId]);

                if (result.rows[0].hosting_lobbies < 1) {
                    var res = client.query.sync(clien, "INSERT INTO lobbies(name, game, owner) VALUES($1, $2, $3)", [req.param("name"), req.param("game"), userId]);
                    res.redirect("/lobby");
                }
                else {
                    errors = "You can only host one lobby at a time";
                }
            }
            else {
                var games = client.query.sync(client, "SELECT * FROM games").rows;

                res.render("lobby/create", {
                    title: "Create a new lobby",
                    games: games,
                    errors: errors
                });
            };
        }
    }, function(err) {
        if(err) {
            // do your error handling here
        }
    });
}