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.
Related
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();
}
});
});
I want to use an module to get and process data from my MongoDB database. (It should generate an object that represents my Express.js site's navbar)
I thought of doing something like this:
var nav = { Home: "/" };
module.exports = function() {
MongoClient.connect(process.env.MONGO_URL, function(err, db) {
assert.equal(err, null);
fetchData(db, function(articles, categories) {
combine(articles, categories, function(sitemap) {
// I got the data. What now?
console.log("NAV: ", nav);
})
});
});
};
var fetchData = function(db, callback) {
db.collection('articles').find({}).toArray(function(err, result) {
assert.equal(err);
articles = result;
db.collection('categories').find({}).toArray(function(err, result) {
assert.equal(err);
categories = result;
db.close();
callback(articles, categories);
});
});
};
var combine = function(articles, categories, callback) {
categories.forEach(function(category) {
nav[category.title] = {};
articles.forEach(function(article) {
if(article.category == category.name) {
nav[category.title][article.title] = "link";
}
})
});
callback(nav);
};
As of line 6, I do have all data I need.
(An object, currenty like { Home: '/', Uncategorized: { 'Hello world!': 'link' } })
But since I'm in an anonymous function, I don't know how to return that value. I mean, return would just return it the function that called it... And in the end, MongoClient.connect would receive my data.
If I set a variable instead, it would be set as module.exports returned before Node can even query the data from the database, right?
What can I do in order to make this work?
It should result in some kind of function, like
var nav = require('nav');
console.log(nav());
Thanks in advance!
Add another callback:
var nav = { Home: "/" };
module.exports = function(cb) {
MongoClient.connect(process.env.MONGO_URL, function(err, db) {
assert.equal(err, null);
fetchData(db, function(articles, categories) {
combine(articles, categories, function(sitemap) {
cb(sitemap);
})
});
})
});
And then use this way:
var nav = require('nav');
nav(function(sitemap){ console.log(sitemap); });
You can use mongoose module or monk module. These modules have been tested properly .
Just use
npm install mongoose or monk
The suggestion about mongoose is great and you can look into it, however I think you've already done the job with the fetching of the data from the db. You just need to access it in your main node flow.
You can try this:
module.exports.generateNav = function() {
MongoClient.connect(process.env.MONGO_URL, function(err, db) {
assert.equal(err, null);
var output = fetchData(db, function(articles, categories) {
combine(articles, categories, function(sitemap) {
})
});
return (output);
});
};
And then in your main application you can call it in the following way:
var nav = require('nav');
navigation = nav.generateNav();
console.log(navigation);
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}); });
});
Considering that my server.js looks almost like this. Just send you the relevant part. I did not receive anything from the query, I do have data in the database, and "sendNotification" is triggered by the jQuery function in the client. Everything works and since var notis = []; returns an empty value and is what is shows as response. I know I have to debug SQL and that's what I'm going to do but anyway want to be sure of this other things. So my questions are:
1) Is a right syntax for node.js, considering this async behavior? (which I still don't understand )
2) The query always should be inside of the "io.sockets.on('connection')" part?
connection = mysql.createConnection({
host: 'localhost',
user: '',
password: "",
database: 'table' //put your database name
}),
...
connection.connect(function(err) {
// connected! (unless `err` is set)
console.log(err);
});
…
var sqlquery = function(uID,vs){
var notis = [];
connection.query("SELECT * FROM notification WHERE kid = ? AND v = ? ORDER BY id DESC",[uID,vs])
.on("result", function (data){
return notis.push(data);
});
};
io.sockets.on('connection', function(socket) {
...
socket.on("sendNotification", function(data) {
var roomBName = data.room_name.replace("room-",""),
found = [];
var roomSelected = _.find(rooms, function (room) { return room.id == roomBName });
for (var person in people) {
for (var i = 0, numAttending = roomSelected.peopleAttending.length; i < numAttending; i++) {
if (people[person].name == roomSelected.peopleAttending[i]) {
found.push(person);
}
}
}
for (var i = 0, numFound = found.length; i < numFound; i++) {
**result = sqlquery(9,2);**
io.to(found[i]).emit('notification', result);
};
});
Your sqlquery() function will not accomplish anything useful. Because connection.query() is asynchronous, that means it provides the response sometime LATER after sqlquery() has already finished.
The only way in node.js to use an async result is to actually use it in the callback that provides it. You don't just stuff it into some other variable and expect the result to be there for you in other code. Instead, you use it inside that callback or you call some other function from the callback and pass it the data.
Here's one way, you could change your sqlquery() function:
var sqlquery = function(uID, vs, callback){
connection.query("SELECT * FROM notification WHERE kid = ? AND v = ? ORDER BY id DESC",[uID,vs])
.on("result", function (data){
callback(null, data);
});
// need to add error handling here if the query returns an error
// by calling callback(err)
};
Then, you could use the sqlquery function like this:
found.forEach(function(person, index) {
sqlquery(..., function(err, result) {
if (err) {
// handle an error here
} else {
io.to(person).emit('notification', result);
}
});
});
And, it looks like you probably have similar async issues in other places too like in connection.connect().
In addition to #jfriend00, this could be done with new ES6 feature Promise :
var sqlquery = function(uID, vs){
return new Promise(function(resolve, reject){
connection.query("SELECT * FROM notification WHERE kid = ? AND v = ? ORDER BY id DESC",[uID,vs])
.on("result", function (data){
resolve(data);
});
});
};
Now you can use it like :
found.forEach(function(person, index) {
sqlquery(...)
.then(function(result){
io.to(person).emit('notification', result);
});
});
Check below algorithm...
users = getAllUsers();
for(i=0;i<users.length;i++)
{
contacts = getContactsOfUser(users[i].userId);
contactslength = contacts.length;
for(j=o;j<contactsLength;j++)
{
phones = getPhonesOfContacts(contacts[j].contactId);
contacts[j].phones = phones;
}
users[i].contacts = contacts;
}
return users;
I want to develop such same logic using node.js.
I have tried using async with foreach and concat and foreachseries functions. But all fail in the second level.
While pointer is getting contacts of one user, a value of i increases and the process is getting started for next users.
It is not waiting for the process of getting contacts & phones to complete for one user. and only after that starting the next user. I want to achieve this.
Actually, I want to get the users to object with proper
Means all the sequences are getting ruined, can anyone give me general idea how can I achieve such a series process. I am open to change my algorithm also.
In node.js you need to use asynchronous way. Your code should look something like:
var processUsesrs = function(callback) {
getAllUsers(function(err, users) {
async.forEach(users, function(user, callback) {
getContactsOfUser(users.userId, function(err, contacts) {
async.forEach(contacts, function(contact, callback) {
getPhonesOfContacts(contacts.contactId, function(err, phones) {
contact.phones = phones;
callback();
});
}, function(err) {
// All contacts are processed
user.contacts = contacts;
callback();
});
});
}, function(err) {
// All users are processed
// Here the finished result
callback(undefined, users);
});
});
};
processUsers(function(err, users) {
// users here
});
You could try this method without using async:
function getAllUserContacts(users, callback){
var index = 0;
var results = [];
var getUserContacts = function(){
getContactsOfUser(users[index].userId, function(contacts){
var index2 = 0;
var getContactsPhones = function(){
getPhonesOfContacts(contacts[index2].contactId, function(phones){
contacts[index2].phones = phones;
if(index2 === (contacts.length - 1)){
users[index].contacts = contacts;
if(index === (users.length - 1)){
callback(users)
} else {
index++;
getUserContacts();
}
}else{
index2++;
getContactsPhones();
}
});
}
getContactsPhones();
});
}
getUserContacts();
}
//calling the function
getAllUsers(function(users){
getAllUsersWithTheirContacts(users, function(usersWithContacts){
console.log(usersWithContacts);
})
})
//Asynchronous nested loop
async.eachSeries(allContact,function(item, cb){
async.eachSeries(item,function(secondItem,secondCb){
console.log(secondItem);
return secondCb();
}
return cb();
},function(){
console.log('after all process message');
});