简体   繁体   中英

Async / await in node nested functions?

I'm trying to get async / await to trigger events in order, but it seems I'm missing something as my console.log markers are triggering in reverse to the order I was hoping for.

I 'm wondering if is to do with my use of nested functions in users.js but having tried multiple variations of async / await , it consistently doesn't work as expected.

// index.js

var users = require("./users.js"); 

app.post("/getToken", async function(req, res) {  
    if (req.body.email && req.body.password) {
        const email = req.body.email;
        const password = req.body.password;
        const user = await users(email, password) 
        // running this should output console.log("No 1") 
        // from users.js first, but doesn't ?

        console.log('No 2')
        if (user) {
            var payload = {
                id: user.id
            };
            var token = jwt.encode(payload, cfg.jwtSecret);
            res.json({
                token: token
            });
        } else {
            res.sendStatus(401);
        }
    } else {
        res.sendStatus(401);
    }
});

// users.js

module.exports = function(emailAddress, password) {
    db.connect();
    var query = `
        SELECT 
            id,
            email,
            password,
            salt
        FROM 
            users 
        WHERE 
            email = ?`;
    var query_params = [emailAddress];

    db.query(
        query, 
        query_params, 
        function(error, result, fields) {
            console.log('No 1')

            if (error) throw error;
            if ( result.length == 1 ) {
                if ( checkPass(password, result[0].password, result[0].salt ) ) {
                    return { id: result[0].id }

                } else {
                    console.log("login False | Password");
                    return false;
                }

            } else {
                console.log("login False | username");
                return false;
            }
        }
    )
}

Your users.js function doesn't return anything. The callbacks you're passing query do, but the overall function doesn't. Since it never returns anything explicitly, the result of calling it is undefined . If you await undefined , it's like await Promise.resolve(undefined) and so your resolution handler is called quite quickly.

You want that function to return a promise that doesn't get resolved until the work is done. Since what it uses is an old-style Node callbck API, it's reasonable to use new Promise to create that promise (alternately, get or create a promise-enabled API to that DB).

I also suspect you're calling connect incorrectly, since normally that would be an asynchronous action, but you're treating it as though it were synchronous.

See comments:

users.js

module.exports = function(emailAddress, password) {
    return new Promise((resolve, reject) => {
        // Use the callback to know when the connection is established
        db.connect(error => {
            if (error) {
                // Connection failed
                reject(error);
                return;
            }
            var query = `
                SELECT 
                    id,
                    email,
                    password,
                    salt
                FROM 
                    users 
                WHERE 
                    email = ?`;
            var query_params = [emailAddress];
            db.query(
                query, 
                query_params, 
                function(error, result, fields) {
                    // Throwing an error here does nothing useful. Instead,
                    // reject the promise.
                    if (error) {
                        reject(error);
                        return;
                    }
                    // Resolve our promise based on what we got
                    if ( result.length == 1 ) {
                        if ( checkPass(password, result[0].password, result[0].salt ) ) {
                            resolve({ id: result[0].id });
                        } else {
                            console.log("login False | Password");
                            resolve(false);
                        }
                    } else {
                        console.log("login False | username");
                        resolve(false);
                    }
                }
            );
        });
    });
}

Then using it:

app.post("/getToken", async function(req, res) {  
    // You must handle errors, since `post` won't do anything with the return
    // value of this function
    try {
        if (req.body.email && req.body.password) {
            const email = req.body.email;
            const password = req.body.password;
            // Now this waits here, since `users` returns a promise that
            // isn't resolved until the query completes
            const user = await users(email, password) 
            console.log('No 2')
            if (user) {
                var payload = {
                    id: user.id
                };
                var token = jwt.encode(payload, cfg.jwtSecret);
                res.json({
                    token: token
                });
            } else {
                res.sendStatus(401);
            }
        } else {
            res.sendStatus(401);
        }
    } catch (e) {
        res.sendStatus(401);
    }
});

The problem is that db.query function is asynchronous - you are providing callback function that is executed when database call is finished. You probably need to wrap this whole function in Promise:

module.exports = function(emailAddress, password) {
  return new Promise(function(resolve, reject) {
    db.connect();
      var query = `
          SELECT 
              id,
              email,
              password,
              salt
          FROM 
              users 
          WHERE 
              email = ?`;
      var query_params = [emailAddress];

      db.query(
          query, 
          query_params, 
          function(error, result, fields) {

              if (error) return reject(error)

              if ( result.length == 1 ) {
                  if ( checkPass(password, result[0].password, result[0].salt ) ) {
                      resolve({id: result[0].id})

                  } else {
                      console.log("login False | Password");
                      reject();
                  }

              } else {
                  console.log("login False | username");
                  reject();
              }
          }
      )
  })
}

You can learn more about Promise API here

EDIT:

So you should additionally make connect synchronous. Here's a piece of code I have refactored for you. It should work just fine. I have used some ES6 elements to make it more readable.

const connect = () => new Promise((resolve, reject) => {
    db.connect((err) => {
      if (err) return reject(err);

      resolve();
    })
  })

const makeDbRequest = (emailAddress, password) => new Promise((resolve, reject) => {
    const query = `
      SELECT 
          id,
          email,
          password,
          salt
      FROM 
          users 
      WHERE 
          email = ?`;

    const query_params = [emailAddress];

    db.query(
        query,
        query_params,
        handleDbData(resolve, reject, password),
    );
  })

const handleDbData = (resolve, reject, password) => (error, result, fields) => {
  if (error) return reject(error)

  if ( result.length == 1 ) {
      if ( checkPass(password, result[0].password, result[0].salt ) ) {
          resolve({id: result[0].id})

      } else {
          console.log("login False | Password");
          reject();
      }

  } else {
      console.log("login False | username");
      reject();
  }
}

module.exports = (emailAddress, password) => new Promise((resolve, reject) => {
    connect()
      .then(() => {
        makeDbRequest(emailAddress, password)
          .then(resolve)
          .catch(reject)
      })
      .catch(reject);
  })

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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