0

I am working on creating a user login system and need to query the database. I am new to coding and I am struggling to understand callbacks and how data can be passed once the callback has happened.

I have a MySQL query that gets called via a callback. The result of this, I then need to use in the following async function body for use in app.js as a promise. The async function sits inside a route file.

I tried creating a variable outside the callback scope as you can see here with users being declared before the .query() but I still receive undefined after it has run. I've also tried using the keyword await before connection.query so the function doesn't run until the callback is completed but that does not seem to work either.

Any help would be greatly appreciated at the minute as I'm still trying to understand how JS works. Thanks

async function getAll() {
var users;
var query = "SELECT * FROM `users`"; // query database to get 
    all the players

   // execute query
   connection.query(query, (err, result) => {
      if (err) {
        res.redirect('error.ejs');
        console.log(error.message);
      }

      users = result;

   });
return users.map(u => {
        const { password, ...userWithoutPassword } = u;
        return userWithoutPassword;
        });

}

The following function is where the async function is then called in app.js:

function getAll(req, res, next) {
    userService.getAll().then(users => {
      console.log(users);
      res.json(users)}).catch(err => next(err)); 
}
Ben Cleary
  • 15
  • 5

2 Answers2

0

You're not showing a bunch of the variables you're using which makes me think you may be using them improperly. In asynchronous code, particular asynchronous code on a server, you should not be using shared or global variables to process a particular request handler. If you do that, you will open yourself up for concurrency issues where two requests tromp on each other. This will create very hard to find and debug bugs in your server.

In Javascript asynchronous callbacks, you continue your logic flow INSIDE the callback because that's the only place you know the value of the data and the timing of the response. You don't assign that to a higher level variable and then somehow use that data in some other scope. That will never work because ONLY inside the callback do you know exactly when that data is valid.

Also, a function only needs to be tagged async when you actually intend to use await with promises inside the function so in this case, there's no real reason for your function to be async as you aren't using promises with your database at all.

If you show us the larger context for how this getAll() function fits into a whole request handler, we could help you design things even better, but I've at least changed the function to pass in res so it can consistently be used in both the error case and the success case to send the response.

Here's a general idea:

// method of userService
function getAll(res) {
    var query = "SELECT * FROM `users`"; // query database to get all the players

    // execute query
    connection.query(query, (err, result) => {
        if (err) {
            res.redirect('error.ejs');
            console.log(error.message);
        } else {
            // do something here to process result from your query
            let users = result.map(u => {
                // not sure what you're trying to do here
                return u.userWithoutPassword;
            });
            // do something with users here
            res.send(...);
        }
    });
    // no code here because this will run BEFORE your query is done
}

// change to pass req to userService.getAll(res)
function getAll(req, res, next) {
    userService.getAll(res);
}

If you want to communicate back a value or success/error from getAll(), then you would need to either pass in a callback that you can call or you would need to return a promise for which you can use the resolved value to communicate back the value.

If you want to read more about returning asynchronous values, see this answer: How do I return the response from an asynchronous call?


My suggestion would be promise based. The mysql2 library has built-in support for promises which you would need to switch over to use exclusively instead of the mysql library. I'd also be entirely consistent on where the response is sent, in error or success:

const msql = require("mysql2/promise");

// userService.getAll()
async function getAll() {
    var users;
    var query = "SELECT * FROM `users`"; // query database to get all the players
    let result = await connection.query(query);
    return result.map(u => {
        const {
            password,
            ...userWithoutPassword
        } = u;
        return userWithoutPassword;
    });
}

// request handler
function getAll(req, res, next) {
    userService.getAll().then(users => {
        console.log(users);
        res.json(users);
    }).catch(err => {
        console.log(error.message);
        res.redirect('error.ejs');
    });
})
jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • I have edited my answer to provide the next code where the async function is used. I was trying to use the global variable to then pass the data into the non-async function to be used there but I now understand why that could be a problem. The `u.userWithoutPassword` returns the data to the function without the users password. – Ben Cleary Jul 23 '19 at 20:57
  • I also tried using your example, and it seems `res` isn't passed into the else statement as I get an error saying "cannot read property 'send' of undefined." – Ben Cleary Jul 23 '19 at 21:15
  • @BenCleary - You have to pass `res` when you call `getAll(res)`. So you have to change where you call it. See what I added to my answer. – jfriend00 Jul 23 '19 at 21:16
  • @BenCleary - Now I can see that your error handling is messed up because `userService.getAll()` does NOT return a promise and even if you leave at as `async` will never reject so your error handling is not correct. It's unclear what you really want to do for errors because you have `res.redirect('error.ejs')` in one place and then `.catch(err => next(err))` in another. Those are two completely different strategies. Which is it? – jfriend00 Jul 23 '19 at 21:20
  • I was trying to redirect to an error page if the query function failed and then catch the error if the promise failed. As you can tell I'm literally two weeks into learning about promises and node js. I think I need to look into putting the data call into a callback function and calling it in the async function. Do you think thats the best way of programming it? – Ben Cleary Jul 23 '19 at 22:20
  • @BenCleary - Well, you weren't using promises in your `userService.getAll()` function so that was misguided. You were using a plain callback for your database query which means you would communicate back the result via a callback. Or, switch to the promise interface on your database (my recommendation) and then you could rewrite the logic and error handling with promises. – jfriend00 Jul 23 '19 at 22:41
  • Is it possible for me to put the query callback inside the synchronous function and then pass the result through the `userService.getAll(users).then(......)` whilst still in the query or is that bad programming? – Ben Cleary Jul 23 '19 at 22:51
  • @BenCleary - I added an implementation for how I'd do this, using promises not plain callbacks. – jfriend00 Jul 24 '19 at 03:20
  • @BenCleary - Since it appears you are relatively new here, if this answered your original question, then you can indicate that to the community by clicking the checkmark to the left of the answer. That will also earn you some reputation points on the site for following the proper procedure which can lead to more privileges on the site over time. – jfriend00 Jul 24 '19 at 15:20
0

Ok so I think I've finally managed to solve my answer using solely callbacks. It would be nice to now progress this to using promises if anyone could help me out with that. But here is my final product (feel free to tear it apart):

The grabUsers() function starts the query and then passes the data into the callback function for use in the next callback.

function grabUsers(callback){
   var query = "SELECT * FROM `users`"; // query database to get all the 
   players
   // execute query
   connection.query(query, (err, result) => {
   if (err) {
        res.redirect('error.ejs');
        console.log(error.message);
    }
    let users = result; 
    callback(null, users);
  })   

}

The getAll function then uses grabUsers() callback to do stuff with the data.

 function getAll(callback) {
    grabUsers(function(err, users){
      if (err) {
        res.redirect('error.ejs');
        console.log(error.message);
      }
      users = users.map(u => {
        const { password, ...userWithoutPassword } = u;
        return userWithoutPassword;
      });
      callback(null, users);
    });
}

The final getAll function on app.js then does some final stuff to the code without needing a callback argument as this is the bottom layer of functions in the chain.

  function getAll(req, res, next) {
     userService.getAll(function(err,users){
     if (err) {
        res.redirect('error.ejs');
        console.log(error.message);
     }
      res.json(users);
      console.log(users);
  })

Hopefully, this helps other people struggling with callbacks and data.

Ben Cleary
  • 15
  • 5
  • There are a number of problems with this implementation: 1) `getAll()` calls `res.redirect()` and never calls the callback. So, errors never call the callback, success does. That's not a good API. 2) You're inconsistent on where you send the response. Error responses are sent in `getAll()`, success responses are sent in `grabUsers()` as the error response in `grabUsers()` never gets called. 3) Your `if (err)` has no else so in an error (if it ever got called) you would try to send two responses. Please see my recommendation I added to my answer. – jfriend00 Jul 24 '19 at 03:23
  • I kind of understand what you are saying, if I added else statements or try-catch statements to these functions would it solve a lot of my problems? Your suggestion is a lot clearer and succinct. If I'm going to move over mysql2 though would you suggest using MongoDB instead? – Ben Cleary Jul 24 '19 at 08:14
  • I'm not an expert on databases, but choosing a database is about matching your specific requirements to the strengths of different databases. If your needs are very simple, then just go with what seems simplest for you to learn and use. – jfriend00 Jul 24 '19 at 15:18