3

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(本机)
  • 表示
  • express-validator(节点验证器的中间件)
  • 护照(身份验证中间件)

检查用户给出的输入是否有效是最小的问题,我会检查我在哪里断言变量并将页面的渲染版本返回给用户打印出错误。

但是,如果我们首先通过验证错误,我们假设“大厅”已准备好插入数据库,然后我想确保用户没有其他大厅打开并且不是另一个大厅的成员。好吧,现在我最终将一个查询放入另一个查询中,理论上我必须将我的视图渲染函数(res.render())放入每个查询回调中,如果查询遇到错误或返回指示用户不被允许的结果创建一个大厅。我不想那样做,而且似乎不太实用。

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

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

有没有正确的方法来做到这一点?

4

2 回答 2

1

您可能想查看一个async库,例如https://github.com/caolan/async. 它有助于构建异步代码,使其不会变成这样的混乱。根据您的要求,有不同的方法,从简单seriesparallel执行到waterfall依赖auto跟踪等。

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

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

于 2012-10-20T18:20:31.690 回答
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
        }
    });
}
于 2012-10-20T19:45:27.900 回答