如果我在嵌套查询函数中设置它,为什么我的 express-session 变量没有被保存?

Bee*_*man 3 session-variables node.js express

以下代码处理对我的应用程序登录页面的发布请求。

如果登录凭据正确,它会将is_logged_in会话变量设置true为以及user_id从数据库获取的用户 ID。

在获取凭据的查询中,如果一切顺利,我还有第二个查询来获取学生 ID。由于一些奇怪的原因,当我尝试设置req.session.student_id变量并记录我没有看到的会话对象时,它只设置了从第二个查询函数外部设置的is_logged_inuser_id变量。

router.post('/login', (req, res) => {
    con.query("SELECT id, username, password FROM authorizedusers;", 
      (err, result) => {
        if(err) throw err;

        for(var i = 0; i < result.length; i++) {
            if(req.body.username === result[i].username 
               && req.body.password === result[i].password){

            con.query(`SELECT id FROM students WHERE user_id = 
              ${result[i].id};`, (err, result) => {

                    if(err) throw err;
                    
                    req.session.student_id = result[0].id;
                });
                
                req.session.is_logged_in = true;
                req.session.user_id = result[i].id;
                return res.redirect('/');
            }

        }
        return res.render('login', {
            msg: "Error! Invalid Credentials!"
        });
    });
});
Run Code Online (Sandbox Code Playgroud)

这就是我记录会话对象时得到的结果。

Session {
  cookie: { path: '/', _expires: null, originalMaxAge: null, httpOnly: true },
  is_logged_in: true,
  user_id: 3
}
Run Code Online (Sandbox Code Playgroud)

但是,如果我尝试将 设置student_id在与其他范围相同的范围内,如下所示:

router.post('/login', (req, res) => {
    con.query("SELECT id, username, password FROM authorizedusers;", 
      (err, result) => {
        if(err) throw err;

        for(var i = 0; i < result.length; i++) {
            if(req.body.username === result[i].username 
              && req.body.password === result[i].password){

                con.query(`SELECT id FROM students WHERE user_id = 
                  ${result[i].id};`, (err, result) => {

                    if(err) throw err;
                    
                });
                
                req.session.student_id = "test";
                req.session.is_logged_in = true;
                req.session.user_id = result[i].id;
                return res.redirect('/');
            }

        }
        return res.render('login', {
            msg: "Error! Invalid Credentials!"
        });
    });
});
Run Code Online (Sandbox Code Playgroud)

它确实有效,这就是我记录会话对象后得到的结果。

Session {
  cookie: { path: '/', _expires: null, originalMaxAge: null, httpOnly: true },
  student_id: 'test',
  is_logged_in: true,
  user_id: 3
}
Run Code Online (Sandbox Code Playgroud)

为什么会发生这种情况,我该如何解决?

更新

jfriend00回答了我的问题,并说我可以通过使用 mysql2 模块而不是 mysql 模块来解决我的问题。

不过,我想使用 mysql 模块。所以如果有人可以回答如何在使用 mysql 模块时解决这个问题,我会很高兴。

jfr*_*d00 5

这是因为con.query()函数调用的非阻塞、异步特性。它启动异步操作,然后执行其后的代码行。然后,稍后的某个时候,它调用它的回调。所以,在你的这段代码中,我添加了日志:

router.post('/login', (req, res) => {
    con.query("SELECT id, username, password FROM authorizedusers;", (err, result) => {
        if(err) throw err;

        for(var i = 0; i < result.length; i++) {
            if(req.body.username === result[i].username && req.body.password === result[i].password){
                con.query(`SELECT id FROM students WHERE user_id = ${result[i].id};`, (err, result) => {
                    if(err) throw err;
                    console.log("got student_id");
                    req.session.student_id = result[0].id;
                });
                req.session.is_logged_in = true;
                req.session.user_id = result[i].id;
                console.log("redirecting and finishing request");
                return res.redirect('/');
            }

        }
        return res.render('login', {
            msg: "Error! Invalid Credentials!"
        });
    });
});
Run Code Online (Sandbox Code Playgroud)

你会得到这个日志:

redirecting and finishing request
got student_id
Run Code Online (Sandbox Code Playgroud)

因此,您在获取之前完成了请求student_id并将其设置到会话中。因此,当您立即尝试使用会话中的数据时,它还不存在。


如果没有承诺,这不是一个特别容易解决的问题,因为您在for循环中有一个老式的异步调用。如果您为 SQL 库使用 promise 接口,这会容易得多,因为这样您就可以对事物进行排序并且await一次只能运行一个查询 - 您当前的循环正在运行来自for并行这是行不通的以便轻松选择一个获胜者并停止其他一切。

如果您切换到mysql2然后使用 promise 版本:

const mysql = require('mysql2/promise');
Run Code Online (Sandbox Code Playgroud)

然后,你可以做这样的事情(我不是 mysql2 的专家,但希望你能看到如何使用 promise 接口来解决你的问题的想法):

router.post('/login', async (req, res) => {
    try {
        const [rows, fields] = await con.query("SELECT id, username, password FROM authorizedusers;");
        for (let i = 0; i < rows.length; i++) {
            if (req.body.username === rows[i].username && req.body.password === rows[i].password) {
                let [students, fields] = await con.query(`SELECT id FROM students WHERE user_id = ${rows[i].id};`);
                req.session.student_id = students[0].id;
                req.session.is_logged_in = true;
                req.session.user_id = rows[i].id;
                return res.redirect('/');
            }
        }
        return res.render('login', { msg: "Error! Invalid Credentials!" });
    } catch (e) {
        console.log(e);
        res.sendStatus(500);
    }
});
Run Code Online (Sandbox Code Playgroud)

您现有的实现也有其他缺陷,可以使用以下代码进行更正:

  1. 声明的循环变量 var在异步回调函数中永远不会正确。
  2. 您的if (err) throw err错误处理不足。您需要捕获错误,记录它并在响应处理程序中收到错误时发送错误响应。
  3. 您将始终res.render()在循环中的任何数据库调用完成之前调用。