leonsPAPA
leonsPAPA

Reputation: 797

How to wait mongoose .exec function to be done?

I am some cofused by asychronous nodejs and mongoose. Simplily, I want to post an array of usernames and check, if a username is in database, then I put it in the valid array, otherwise, put it in the invalid array.

Here is my current code:

var User = require('../../db/models/user');

api.post('/userlist', function(req, res) {

  var invalid = []; // usernames which can not be found in database
  var valid = []; // usernames which can be found in database

  (req.body.userlist).forEach(function(username) {

    User
      .findOne({username: username})
      .exec(function(err, user) {

        if (err) {
          res.send(err);
          return;

        } else if (!user) {

          invalid.push(username);

        } else {

          valid.push(req.params.item);
        }
      });
  });

  res.send({
    Invalid: invalid,
    Valid: valid
  });

});

When I executed the above code, it outputs the intial empty array directly.

Invalid: [],
Valid: []

I know it is because nodejs first execute this res.send then execute function .exec(function(err, user), but i do not know how to get the right invalid and valid array, pls advise.

Upvotes: 0

Views: 1317

Answers (3)

chrisbajorin
chrisbajorin

Reputation: 6153

While these other solutions solve what you're trying to accomplish, they still incorporate bad design by iterating findOne(). Executing 1 query for every item in your list is incredibly inefficient. Using an $in query and a basic map, you can use a single query:

var User = require('../../db/models/user');

api.post('/userlist', function(req, res) {

    User.find({username: {$in: req.body.userlist}}, function(err, users) {

        if (err) {
            return res.send(err);
        }

        // create a map of all the users in your list that exist in your database
        var dbUserMap = {};
        users.forEach(function(user) {
            dbUserMap[user.username] = true;
        });

        var valid = [];
        var invalid = [];

        // check your POST list against the database map
        req.body.userlist.forEach(function(username){

            if (dbUserMap[username]) {
                valid.push(username);
            }
            else {
                invalid.push(username);
            }
        });

        res.send({
            valid: valid,
            invalid: invalid
        });
    });
});

Upvotes: 2

Madara's Ghost
Madara's Ghost

Reputation: 174967

Your best bet is to use a promise:

api.post('/userlist', (req, res) => {
    // Takes a username and returns a promise for information on that username.
    function findByUsername(username) {
        return new Promise((resolve, reject) =>
            User.findOne({username}).exec((err, user) => 
                err ? reject(err) : resolve(user)
            )
        );
    }

    // Iterate the array and transform each user to a promise for data on that user. 
    Promise.all(req.body.userlist.map(findByUsername))
        // Then, when all of the promises in that new array resolve
        .then(allUserDataInOrder => {
            // Find all the valid ones (if (user))
            let Valid = allUserDataInOrder.filter(Boolean); // Only those who are truthy
            // And all the invalid ones (if (!user))
            let Invalid = allUserDataInOrder.filter(userData => !userData); // Sadly, no convenient function here :(
            // And send both away
            res.send({Valid, Invalid}); // Short syntax FTW!
        })
        .catch(res.send); // Called with error object if any.
});

Upvotes: 5

Lisa Gagarina
Lisa Gagarina

Reputation: 693

Try to use async module:

var invalid = [];
var valid = [];

async.each(req.body.userlist, function(name, next) {
  User.findOne({username: name}, function(err, user) {
    if (err) {
     return next(err);
    }
    if (!user) {
      invalid.push(name);
    } else {
      valid.push(name);
    }

    next();
  )};
}, function(err) {
   if (err) {
     return res.send(err);
   }

   res.send({
     Invalid: invalid,
     Valid: valid
   });
});

Upvotes: 1

Related Questions