Reputation: 797
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
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
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
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