0

So I'm making a web application and I'm trying to send variables to an EJS file but when they are sent out of the mongo functions they come out as undefined because it's a different scope for some reason. It's hard to explain so let me try to show you.

router.get("/", function(req, res){
    var bookCount;
    var userCount;
    Books.count({}, function(err, stats){
      if(err){
        console.log("Books count failed to load.");
      }else{
        bookCount = stats;
      }
    });
    User.count({}, function(err, count){
        if(err){
          console.log("User count failed to load.")
        }else{
          userCount = count;
          console.log(userCount);
        }
    });
    console.log(userCount);
    //Get All books from DB
    Books.find({}, function(err, allbooks){
        if(err){
            console.log("Problem getting all books");
        }else{
            res.render("index", {allbooks: allbooks, bookCount: bookCount, userCount: userCount});
        }
    });
});

So in the User.Count and Books.count I'm finding the number of documents in a collection which works and the number is stored inside of the variables declared at the very top.

After assigning the numbers like userCount i did console.log(userCount) which outputs the correct number which is 3, If was to do console.log(userCount) out of the User.count function it would return undefined, which is a reference to the declaration at the very top.

What is really weird is that Book.Find() has the correct userCount even though its a totally different function. The whole goal im trying to accomplish is doing res.render("index", {userCount: userCount}); outside of the Books.find(). I can do it but of course for some reason it passes undefined instead of 3. I hope this made a shred of sense.

parwatcodes
  • 6,669
  • 5
  • 27
  • 39
  • Dupe of https://stackoverflow.com/questions/23667086/why-is-my-variable-unaltered-after-i-modify-it-inside-of-a-function-asynchron – JohnnyHK Mar 14 '17 at 02:39

2 Answers2

0

I seem to have found a solution. but if anyone knows a different way I would love to know. So basically all you need to do is move the User.Count function outside of the router.get() function. Not completely sure about the logic of that but it works...

0

This is a classic asynchronous-operation problem: Your methods (Books.count, Books.find, User.count) are called immediately, but the callback functions you pass to them are not. userCount is undefined in your log because console.log is called before the assignment in the callback function is made. Your code is similar to:

var userCount;

setTimeout(function() {
    userCount = 3;
}, 1000);

console.log(userCount); // undefined

User.count takes time to execute before calling back with the result, just like setTimeout takes the specified time to execute before calling its callback. The problem is JS doesn't pause and wait for the timeout to complete before moving on and calling console.log below it, it calls setTimeout, calls console.log immediately after, then the callback function is called one second later.

To render a complete view, you need to be sure you have all of the data before you call res.render. To do so you need to wait for all of the methods to call back before calling res.render. But wait, I just told you that JS doesn't pause and wait, so how can this be accomplished? Promise is the answer. Multiple promises, actually.

It looks like you are using Mongoose models. Mongoose has been written so that if you don't pass a callback function to your methods, they return a promise.

Books.count({}) // returns a promise

JS promises have a method then which takes a callback function that is called when the promise has been resolved with the value of the asynchronous method call.

Books.count({}) // takes some time
    .then(function(bookCount) { // called when Books.count is done
        // use the bookCount here
    })

The problem is, you want to wait for multiple operations to complete, and multiple promises, before continuing. Luckily JS has a utility just for this purpose:

Promise.all( // wait for all of these operations to finish before calling the callback
    Books.count({}),
    User.count({}),
    Books.find({})
)
    .then(function(array) { // all done!
        // the results are in an array
        bookCount = array[0];
        userC0unt = array[1];
        allBooks = array[2];
    })
Luke Chinworth
  • 111
  • 1
  • 6