I am trying to run a find inside another find and I am not getting any results from the second find operation.
User.find({}, function (err, docs) {
for (i = 0; i < docs.length; i++) {
var tmp = '';
UserGroups.find({userName: docs[i].userName}, function (errin, groups) {
for (g = 0; g < groups.length; g++) {
tmp += ", " + groups[g].groupName;
//console.log(groups[g].groupName);
}
});
console.log(tmp);
//docs[i].group = that;
docs[i].username = decrypt(docs[i].username);
docs[i].password = '';
}
res.render('users', {users: docs});
});
Your UserGroups.find is going to be run asynchronously therefore console.log(tmp) is going to be run before your UserGroups.find has a chance to finish and your call is going to return before you get any results. If you want the results of the UserGroup.find you need to move all of your logic into that callback.
EDIT
This is I believe a far better approach in terms of predictability and query performance. Your previous approach the UserGroup.find being called n number of times. N being the number of users in your database. This approach the database is only queried twice. Once to get all the users and second to get all the groups.
User.find({}, function (err, docs) {
//Get all the usernames before executing the UserGroups query
var userNames = [];
users.forEach(function(element) {
userNames.push(element.userName);
});
UserGroups.find({userName: {$in : userNames}}, function (errin, groups) {
for (var i = 0; i < docs.length; i++) {
//get all the groups that belong to this user
var userGroups = groups.filter(function(value) {
return value.userName === docs[i].userName;
});
var tmp = "";
userGroups.forEach(function(element){
tmp += "," + element.groupName
});
//docs[i].group = that;
docs[i].username = decrypt(user[i].username);
docs[i].password = '';
}
res.render('users', {users: docs});
});
});
Also since it appears you are using Mongoose you can use the built in populate feature in Mongoose to "join" collections together
Related
I have images model and users model.
every image has a user_id field of a user and I want to get the picture of the user and name, add it to the image object and return an array of images.
When I am trying to add author_image field to ONE image I don't have any errors,
But when I am looping over all the images the app crashes the output is that imagesData is undefined as well as userData.
I tried using promises but again I get an error.
What is the best way I can do that without the undefined error?
router.route('/images/all')
.get(function(req,res){
var response = {};
var imagesData = {};
images.find({}).lean().exec(function(err,data){
// console.log(data);
imagesData = data;
if (!err) {
for (var i = 0; i < imagesData.length; i++) {
users.find(({'_id': imagesData[i].user_id}),function(err,userData){
console.log(userData);
imagesData[i].author_pic = userData[0].profile_image;
});
}
}
res.json(imagesData);
});
});
What you missed out is that find operation is not a synchronous operation. So all your find operation immediately move on to the next line.
Although there are multiple ways to handle such situation, I tend to use promises (Q library).
The code would look like this
var Q = require('q');
var defer = Q.defer();
images.find({}).lean().exec(function (err, data) {
// console.log(data);
imagesData = data;
var promiseArr = [];
if (!err) {
for (var i = 0; i < imagesData.length; i++) {
var innerDefer = Q.defer();
users.find(({'_id': imagesData[i].user_id}), function (err, userData) {
console.log(userData);
defer.resolve(userData[0].profile_image);
});
promiseArr.push(innerDefer);
}
}
Q.all(promiseArr).then(function (results) {
for (var i = 0; i < imagesData.length; i++) {
if (Q.isPromise(results[i])) {
results[i] = results[i].valueOf();
}
imagesData[i].author_pic = results[i];
}
res.json(imagesData);
})
});
In this case I am using the Q.all method which basically waits for all the find to finish, and executes only then.
So let's say I have the following for loop
for(var i = 0; i < array.length; i++){
Model.findOne({ _id = array[i].id}, function(err, found){
//Some stuff
});
}
How do I make this code work? Every time I run it I get array[i] = undefinedbecause the mongo-db query is asynchronous and the loop has already iterated 5 times by the time the first query is even completed. How do I go about tackling this issue and waiting for the query to complete before going on to the next iteration?
This doesn't specifically answer your question, but addresses your problem.
I'd use an $in query and do the filtering all at once. 20 calls to the db is pretty slow compared to 1:
// grab your ids
var arrayIds = myArray.map(function(item) {
return item._id;
});
// find all of them
Model.find({_id: {$in: arrayIds}}, function(error, foundItems) {
if (error) {
// error handle
}
// set up a map of the found ids
var foundItemsMap = {};
foundItems.forEach(function(item) {
foundItemsMap[item._id] = true;
});
// pull out your items that haven't been created yet
var newItems = [];
for (var i = 0; i < myArray.length; i++) {
var arrayItem = myArray[i];
if ( foundItemsMap[arrayItem._id] ) {
// this array item exists in the map of foundIds
// so the item already exists in the database
}
else {
// it doesn't exist, push it into the new array
newItems.push(arrayItem);
}
}
// now you have `newItems`, an array of objects that aren't in the database
});
One of the easiest ways to accomplish something like you want is using promises. You could use the library q to do this:
var Q = require('q');
function fetchOne(id) {
var deferred = Q.defer();
Model.findOne({ _id = id}, function(err, found){
if(err) deferred.reject(err);
else deferred.resolve(found);
});
return deferred.promise;
}
function fetch(ids, action) {
if(ids.length === 0) return;
var id = ids.pop();
fetchOne(id).then(function(model) {
action(model);
fetch(ids, action);
});
}
fetch([1,2,3,4,5], function(model) { /* do something */ });
It is not the most beautiful implementation, but I'm sure you get the picture :)
Not sure if this is the right way, it could be a bit expensive but this how i did it.
I think the trick is to pull all your data and then looking for an id match.
Model.find(function(err, data) {
if (err) //handle it
for (var i=0; i<array.length; i++) {
for (var j=0; ij<data.length; j++) {
if(data[j].id == array[i].id) {
// do something
}
}
}
}
I'm using Node.js with MongoDB, to be more specific, MongoLab and Mongoose.
In the DB, I have two collections, pours and users. A user object would be linked to multiple pour objects with a shared cid.
I need to iterate through an array of user objects. But for loop somehow doesn't work with asyn functions that I would like to use to modify my array.
express.leaderboard = new Array();
mongoose.Users.find(function(err, users) {
for (var i = 0; i < users.length; i++) {
express.leaderboard[express.leaderboard.length] = users[i];
};
for (i = 0; i < express.leaderboard.length; i++){
updateOunces(i, function(a, fluidOunces){
console.log(a);
express.leaderboard[a].set('totalOunces', fluidOunces);
});
}
});
And this is my function that would retrieve the total fluidOunces for a user.
function updateOunces(i, callback){
//console.log(express.leaderboard[b].cid);
mongoose.Pours.find({
"cid": express.leaderboard[i].cid
}).exec(function(err, result) {
var userOunces = 0.0;
if (!err) {
for (i = 0; i < result.length; i += 1) {
for(j = 0; j < result[i].pour.length; j += 1){
userOunces += result[i].pour[j].fluidOunces;
}
}
callback(i, userOunces);
return;
express.leaderboard[i].set ('totalOunces' , userOunces);
} else {
console.log(err)
};
});
};
Is there a way to iterate and add a new property to each object in the leaderboard array? Using ASYN? Thank you!
use async library.
Example)
mongoose.Users.find(function(err, users) {
async.each(users, function(user, callback) {
// perform updates here, per user.
callback();
}, function(err) {
// everything is complete.
});
});
I'm new to Node.js and Async coding. I need to write the equivalent of a nested for loop which will work with Node. I understand that my question is very similar to the one posted here: nested loops asynchronusly in nodejs, next loop must start only after one gets completed, but even after looking at that post in detail, I was unable to fix my code.
I am working with an XML feed. The 'parser' uses the xml2js package. The
loop runs exactly as expected if I remove the sql query (for which I'm using the mysql node package), but when I put the sql query in, then all the orders get processed first, the the "DONE" is output, and then the query fails as it tries to look up items for just the last order repeatedly.
I've tried replacing the for loops with async.forEach loops, but this did not help.
Any help or advice on how to recode this in a way more idiomatic to node would be greatly appreciated.
Many thanks!
Sixhobbits
parser.parseString(data, function (err, result) {
if(err) throw(err);
var numOrders = result['Root']['Orders'][0]['Order'].length;
var curr, currItem, currOrdId, items, sellersCode;
console.log("Logging IDs of", numOrders, "orders");
// for each order
for (var j=0; j<numOrders; j++){
//current order
curr = result['Root']['Orders'][0]['Order'][j];
currOrdId = curr['OrderId'][0]
items = curr['Items'][0]['Item'];
console.log("Order ID:", currOrdId, "--",items.length, "Items");
// for each item
for (var k=0; k<items.length; k++){
currItem = items[k];
sellersCode = currItem['SellersProductCode'][0];
var sqlQuery = 'select data_index, fulltext_id, cataloginventory_stock_item.product_id from catalogsearch_fulltext inner join cataloginventory_stock_item where catalogsearch_fulltext.data_index like "' + sellersCode + '|%"' + 'and cataloginventory_stock_item.item_id = catalogsearch_fulltext.product_id';
var query = connection.query(sqlQuery,function(err,rows,fields){
if (err) throw(err);
console.log(" Item ID :",currItem['ItemId'][0]);
console.log(" Full Text ID :", rows[0]['fulltext_id']);
console.log(" Product ID :", rows[0]['product_id']);
});
}//for
}//for
console.log("DONE");
});//parseString
You were on the right track by looking to use async.forEach. Here's how you would rework this code to use that:
parser.parseString(data, function (err, result) {
if(err) throw(err);
var numOrders = result['Root']['Orders'][0]['Order'].length;
var currOrdId, items, sellersCode;
console.log("Logging IDs of", numOrders, "orders");
// for each order
async.forEach(result['Root']['Orders'][0]['Order'], function (curr, callback1) {
currOrdId = curr['OrderId'][0];
items = curr['Items'][0]['Item'];
console.log("Order ID:", currOrdId, "--",items.length, "Items");
async.forEach(items, function (currItem, callback2) {
sellersCode = currItem['SellersProductCode'][0];
var sqlQuery = 'select data_index, fulltext_id, cataloginventory_stock_item.product_id from catalogsearch_fulltext inner join cataloginventory_stock_item where catalogsearch_fulltext.data_index like "' + sellersCode + '|%"' + 'and cataloginventory_stock_item.item_id = catalogsearch_fulltext.product_id';
var query = connection.query(sqlQuery,function(err,rows,fields){
console.log(" Item ID :",currItem['ItemId'][0]);
console.log(" Full Text ID :", rows[0]['fulltext_id']);
console.log(" Product ID :", rows[0]['product_id']);
callback2(err);
});
}, callback1);
}, function (err) {
console.log("DONE");
});
});//parseString
Each iteration of async.forEach must call its callback parameter when all of its async processing has completed. You've got two levels in this case which makes it a little more difficult to keep track of in your head, but it's the same concept.
This is a classic closure-in-a-loop problem. You need to break the closure by passing currItem as an argument:
for (var k=0; k<items.length; k++){
currItem = items[k];
sellersCode = currItem['SellersProductCode'][0];
var sqlQuery = 'select data_index, fulltext_id, cataloginventory_stock_item.product_id from catalogsearch_fulltext inner join cataloginventory_stock_item where catalogsearch_fulltext.data_index like "' + sellersCode + '|%"' + 'and cataloginventory_stock_item.item_id = catalogsearch_fulltext.product_id';
var query = connection.query(sqlQuery,(function(CI){
return function(err,rows,fields){
if (err) throw(err);
console.log(" Item ID :",CI['ItemId'][0]);
console.log(" Full Text ID :", rows[0]['fulltext_id']);
console.log(" Product ID :", rows[0]['product_id']);
}
})(currItem)); // Break closure by passing currItem as argument
}//for
I realize this is an old post, but you might find this function useful
eachKVAsync = function(elements,userInfo,onKeyValue,ondone) {
var onDone = ondone;
var ret = null;
var done=false;
var isArray = typeof elements.forEach===$f$;
var keys = isArray ? null : [],
values = isArray ? elements : [];
if (keys) {
for (var k in elements) {
keys.push(k);
values.push(elements[k]);
}
}
var aborted=false;
var endLoop = function (userInfo){
aborted=true;
if (onDone) {
onDone(userInfo,aborted);
onDone = null;
}
}
var i = 0;
var iterate = function (userInfo) {
if (i < values.length) {
var ix=i;
i++;
onKeyValue((keys?keys[ix]:i),values[ix],userInfo,iterate,endLoop);
} else {
if (onDone) {
onDone(userInfo,aborted);
onDone = null;
return;
}
}
}
iterate(userInfo);
},
use example
eachKVAsync(
elements, {
aValue: 2004
},
function onItem(key, value, info, nextItem, endLoop) {
if (value.isOk) {
info.aValue += value.total;
setTimeout(nextItem,1000,info);
} else {
endLoop(info);
}
},
function afterLastItem(info, loopEndedEarly) {
if (!loopEndedEarly) {
console.log(info.aValue);
}
}
);
I use the following code to loop insert 1000000 documents to mongodb,but i found node process takes up a lot of memory,my client are dead.
db.collection("batch_insert", function (err, collection) {
if (!err) {
var count = 0;
for (var i = 0; i < 1000000; i++) {
collection.insert({hello:'world', ok:'OKOKOK'}, {safe:true, serializeFunctions:false}, function (err, result) {
count++;
if (1000000 == count) {
db.close();
}
});
}
} else {
console.log(err);
}
});
Your for cycle blocks event loop. And it can't go to nextTick and handle query results until all queries sended to mongodb. You need to use asynchronous way to batch insert data.
Something like this:
var mongo = require('mongodb');
var Inserter = function (collection) {
this.collection = collection;
this.data = [];
this.maxThreads = 6;
this.currentThreads = 0;
this.batchSize = 5000;
this.queue = 0;
this.inserted = 0;
this.startTime = Date.now();
};
Inserter.prototype.add = function(data) {
this.data.push(data);
};
// Use force=true for last insert
Inserter.prototype.insert = function(force) {
var that = this;
if (this.data.length >= this.batchSize || force) {
if (this.currentThreads >= this.maxThreads) {
this.queue++;
return;
}
this.currentThreads++;
console.log('Threads: ' + this.currentThreads);
this.collection.insert(this.data.splice(0, this.batchSize), {safe:true}, function() {
that.inserted += that.batchSize;
var currentTime = Date.now();
var workTime = Math.round((currentTime - that.startTime) / 1000)
console.log('Speed: ' + that.inserted / workTime + ' per sec');
that.currentThreads--;
if (that.queue > 0) {
that.queue--;
that.insert();
}
});
}
};
var db = new mongo.Db('test', new mongo.Server('localhost', 27017, {}), {native_parser:false});
db.open(function(err, db) {
db.collection('test', function(err, collection) {
var inserter = new Inserter(collection);
setInterval(function() {
for (var i = 0; i < 5000; i++) {
inserter.add({test:'test'});
}
inserter.insert();
}, 0);
});
});
mongodb, just like any other database, takes some time to process requests. You're throwing a million requests at it, and since nothing in your code blocks, that means that at any time a whole bunch of them are going to be queued up somewhere (most likely in multiple places, with some of them inside the driver's code, others inside node's event loop). That takes more than a little bit of memory.
If the queuing didn't happen, you'd either block or drop some of the requests. There Ain't No Such Thing As A Free Lunch.