I have following two collection:
1) Users: {name: xyz,
email:abc#xuz.com}
2) Posts: {_id: 12345678,
time:asdfg,
authEmail:abc#xyz.com,
description: asdigligvilud}
Here i want to get the details of each post along with the corresponding name of author id.
So i wrote query like this:
// get all the posts
Posts.find((err, posts)=> {
if (err) { next(err) };
var data = Array();
var count = 0;
var len = posts.length;
// function to check the end of inner queries
var checkloop = function(){
count++;
if(count===len)
return res.json({result:data,
msg:'success'});
}
for(var i=0;i<len;i++){
Users.findOne({email:posts[count].authEmail},(err,usr)=>{
if(usr){
var item = {
'authorName':usr.name?
'email':usr.email,
'postDesc':posts[count].desc,
'creationTime':posts[count].time
}
data.push(item);
}
checkloop();
});
}
});
But i am getting same result for each iterations that is the post deatils and author id corresponding to the very first loop that is posts[0] only.
So i think its because of asynchronous nature of queries. Is there any standard way to make such query in mongodb or do i need to change my callback method?
Thanks in advance.
I believe the issue you had was because count inside the for loop probably was meant to be i. In addition, each loop iteration captures the same variable i in the Users.findOne callback, but the callback should be able to refer to the post that was queried. Without making much changes, I think this can work with using forEach over the returned posts like below:
// get all the posts
Posts.find((err, posts)=> {
if (err) { next(err) };
var data = Array();
var count = 0;
var len = posts.length;
// function to check the end of inner queries
var checkloop = function(){
count++;
if(count===len)
return res.json({result:data,
msg:'success'});
}
posts.forEach((post) => {
Users.findOne({email:post.authEmail},(err,usr)=>{
if(usr){
var item = {
'authorName':usr.name?
'email':usr.email,
'postDesc':post.desc,
'creationTime':post.time
}
data.push(item);
}
checkloop();
}
});
});
Related
I'm new to node.js and currently working on a project using keystonejs cms and MongoDB. Now I'm stuck in getting data related to multiple collections. Because of this callback functions, I couldn't return an array with relational data. My code something similar to this sample code.
var getAgenda = function(id, callback){
callback = callback || function(){};
if(id){
AgendaDay.model.find({summit:id}).exec(function (err, results3) {
var arr_agenda = [];
var arr_agenda_item = [];
for(var key3 in results3){
AgendaItem.model.find({agendaDay:results3[key3]._id}).exec(function (err, results2){
for(var key2 in results2){
arr_agenda_item.push(
{
item_id: results2[key2]._id,
item_name: results2[key2].name,
from_time: results2[key2].time_from,
to_time: results2[key2].time_to,
desc: results2[key2].description,
fatured: results2[key2].featured,
}
);
}
arr_agenda.push(
{
name: results3[key3].name,
date: results3[key3].date,
description: results3[key3].description,
item_list:arr_agenda_item
}
);
return callback(arr_agenda);
});
}
});
}
}
exports.list = function (req, res) {
var mainarray = [];
Summit.model.find().exec(function (err, resultssummit) {
if (err) return res.json({ err: err });
if (!resultssummit) return res.json('not found');
Guest.model.find().exec(function (err, resultsguset) {
for(var key in resultssummit){
var agen_arr = [];
for(var i=0; i<resultssummit[key].guests.length; i++){
var sumid = resultssummit[key]._id;
//this is the function im trying get data and assign to mainarray
getAgenda(sumid, function(arr_agenda){
agen_arr = arr_agenda;
});
mainarray.push(
{
id: resultssummit[key]._id,
name: resultssummit[key].name,
agenda_data: agen_arr,
}
);
}
res.json({
summit: mainarray,
});
}
});
}
}
If anyone can help me out, that would be really great :)
You need to restructure this whole thing. You should not be calling mongo queries in a for loop and expecting their output at the end of the loop. Also, your response is in a for loop. That won't work.
I'll tell you how to do it. I cannot refactor all of that code for you.
Instead of putting mongodb queries in a for loop, you need to convert it in a single query. Just put the _ids in a single array and fire a single query.
AgendaItem.model.find({agendaDay:{$in:ARRAY_OF_IDS}})
You need to do the same thing for AgendaDay.model.find({summit:id}) as well.
I don't get results from nested query, loc is always null. The query parameter has proper value when I print it, and the database collection 'users' has documents with ids from the array friendsP.
var acquireFriendsPositions = function(db, id, res, callback) {
var cursor = db.collection('users').find({"_id" : new ObjectId(id)}, {_id:0, friends:1});
cursor.each(function(err, doc) {
assert.equal(err, null);
if (doc != null) {
friendsP = doc.friends;
console.log(friendsP); //I get the array friendsP
for(var i =0; i<friendsP.length; i++)
{
console.log(friendsP[i]); //friendsP[i] has proper value
var curs = db.collection('users').find({"_id" : new ObjectId(friendsP[i])}); //but query returns null
curs.each(function(err, loc) {
//assert.equal(err, null);
if(loc!= null) {
console.log(loc);
friendsPos.push(loc);
}
else {
console.log("else");
}
});
}
promise(friendsPos, res); //here i wait for friendsPos and use it in res.send(), but friendsPos is empty because loc is always null
} else {
callback(); //callback does db.close();
}
});
};
If this is the exact code that you are using I suspect that the friendsP value gets hoisted and overwritten in the next each cycle. Meaning that you should be able to fix this by simply changing the code to var friendsP = doc.friends so the friendsP variable is in the function scope. If this is what is happening this is a nasty bug and you should always the declare the variables with a local scope to prevent this from happening.
Try using this for casting Object Id:
var mongodb = require('mongodb');
mongodb.ObjectID.createFromHexString(friendsP[i]);
Thank you, guys. Actually, the problem was callback() which was closing the connection before queries were executed. Here is my new code:
var acquireFriendsPositions = function(db, id, res, callback) {
db.collection('users').findOne({"_id" : new ObjectId(id)},
function(err, item) {
var friendsP = item.friends;
var locFriends = [];
promise(locFriends, res);
var x = 0;
for(i =0; i<friendsP.length; i++)
{
db.collection('users').findOne({"_id" : friendsP[i]}, function(err,subItem){
x=x+1;
//console.log(subItem);
locFriends.push(subItem);
if(x==friendsP.length)
callback();
});
}
});
};
I have a problem with call back functions and loops in nodejs how can I do so that the response should be send after the call back functions execution completion
app.post('/data', function(req,res){
var send = []
for (var i =0; i < p_objReq.body.quantity; i++) {
Instruments.count({//condetion}, function(err, count){
//using count and other parameters I am generating the code which is unique
Instruments.find({id: 'rondom_generated_code'},function(err, instrumentsCount){
if(instrumentsCount.length == 0){
send.push(rondom_generated_code)
if(send.length == p_objReq.body.quantity)
p_objRes.json({success : true, data : send})
}
else{
Instruments.count({//condetion}, function(err, count){
//using count and other parameters I am generating the code which is unique
send.push(rondom_generated_code)
if(send.length == p_objReq.body.quantity)
p_objRes.json({success : true, data : send})
})
}
})
})
}
})
when i wrote like this its sending the same random code that is last generated one. I tried removing the whole thing and written in function and called back but its also not working
One solution is to use Q.js, which is one of Promise library. More APIs of Q.js, please refer to the link in it. Here is one sample codes may could help you, If I catch you correctly for your question.
var Promise = require('Q');
app.post('/data', function(req,res){
var send = [];
var p = function () {
var deferred = Q.defer();
Instruments.count({condetion}, function(err, count){
//using count and other parameters I am generating the code which is unique
if (err) {
deferred.reject(new Error(error));
} else {
send.push(randomnumber)
}
});
return deferred.promise;
}
var ps = [];
for (var i =0; i < p_objReq.body.quantity; i++) {
ps.push(p);
}
Q.all(ps).then(function(){ res.json({success : true, data : send}); });
});
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
}
}
}
}
There are lot of questions like this, but unfortunately I dont find any of them fitting my case.
Closest is Create a JSON tree in Node.Js from MongoDB but it still doesnt work as expected.
Or maybe my head cant wrap this problem...
I have schema that key components for my problem looks like this:
var userSchema = new Schema({
_id: {type: Number},
children: [{type: Number, ref: 'User'}]
)};
each user may have three children users, so it can go infinately deep.
Fortunately, i have to cover two scenarios -
build json tree from specific user up to 3 nestings
calculate data for 10 nestings from specific root.
I tried to write recursive function like this in my express.js api:
api.get('/user/tree/:user_id', function (req, res) {
var user_id = req.params.user_id;
var depth = 0;
var root = {};
function walker(parent) {
if (depth >= 3) {
return res.send('whole data, not just last user'); // this is wrong. it will try to res.send for each iteration of forEach, and it sends only last user.
}
depth += 1;
_.forEach(parent.mlm.childs, function (userid, index) {
User.findOneAsync({_id: userid}).then(function(user) {
parent.mlm.childs[index] = user;
walker(parent.mlm.childs[index]);
});
});
}
User.findOneAsync({_id: user_id}).then(function(user) {
root = user;
walker(user, root);
});
});
but of course it only traverse the tree, instead of traverse and create whole JSON.
Im stuck on how to be able to access the root and send whole tree.
problem of sending many res.send can be solved by counting iterations and send only if forEach ended, i guess.
Thanks for any help.
Ok. I found a solution.
api.get('/user/tree/:user_id', function (req, res) {
var user_id = req.params.user_id;
var tree = {};
var counter = 0;
var gloCounter = 0;
function walker(parent) {
gloCounter += 1;
if (parent.mlm.childs.length === 0 && gloCounter > counter) {
res.send(tree);
return;
}
_.forEach(parent.mlm.childs, function (child, i) {
counter += 1;
User.findOneAsync({_id: child})
.then(function(child) {
parent.mlm.childs[i] = child;
var newParent = parent.mlm.childs[i];
walker(newParent);
}).catch(function(err) {
console.log('error: ' + err);
});
});
}
User.findOneAsync({_id: user_id})
.then(function(user) {
tree = user;
walker(tree);
}).catch(function(err) {
console.log('err: ' + err);
});
});
It works as expected - traverse through whole structure, and create json, that is sent back.
It uses lodash and bluebird promises, for those who would solve similar problem in future and dont understand what is happening with all those "Async" sufixes and _.forEach.