MongoDB findOne error in for loop - node.js

function ParseOrderSchema(CartItems , callback)
{
var lookup = 0;
var subOrderList = new Array();
for(var i=0;i<CartItems.length;i++)
{
Meal.findOne({ _id: CartItems[i].id }).lean().exec(function (err, meal) {
console.log(CartItems[i]);
//meal.mealQTY = CartItems[i].qty;
var s = new subOrder({ meals: meal, deliveryDate: getMomentDate(0) });
subOrderList.push(s);
if (++lookup == CartItems.length) callback(subOrderList);
});
}
}
At CartItem[i].id it works fine and is able to work fine. But it fails at this line meal.mealQTY = CartItems[i].qty;
It can't recognize CartItems[i] inside the findOne() method.

Because findOne is async, i will always be CartItems.length inside the callback as the for loop runs to completion before any of the findOne callbacks occur.
You can fix this by iterating over CartItems using forEach instead so that each iteration's element is captured in a local function parameter:
function ParseOrderSchema(CartItems, callback) {
var lookup = 0;
var subOrderList = new Array();
CartItems.forEach(function(cartItem) {
Meal.findOne({ _id: cartItem.id }).lean().exec(function (err, meal) {
console.log(cartItem);
meal.mealQTY = cartItem.qty;
var s = new subOrder({ meals: meal, deliveryDate: getMomentDate(0) });
subOrderList.push(s);
if (++lookup == CartItems.length) callback(subOrderList);
});
});
}

Related

Assign keystonejs callback function data to array

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.

Send parameters between promises

Im doing a asynchronous process with node.js. using promises. My code is like this:
var net = require('net');
var sqlite3 = require('sqlite3').verbose();
var db = new sqlite3.Database('MyBBDD.db');
var net = require('net');
var Q = require("q");
var firstFunction = function(v_user, v_mystring){
var deferred = Q.defer();
var mi;
stmt = db.prepare("SELECT text1 FROM my_table WHERE user = ?");
stmt.bind (v_user);
stmt.get(function(error,row){
if(!error && row){
deferred.resolve({string: v_mystring, query: row.text1});
}
deferred.reject(new Error(error));
});
return deferred.promise;
};
var secondFunction = function(result){
console.log(result.string);
console.log(result.query);
};
firstFunction('user000','Hello').then(secondFunction);
All in my code work fine but now, I want to concatenate in secondFunction my string received from firstFunction with other string for example "MyNewString".
Somebody know how can I solve it? Can I send "MyNewString" from my firstFunction to my secondFunction?
Thanks in advance.
Best regards.
The best to solve it will be resolve promise with object. Instead of returning just one value - result of querying DB you can return object that covers needed value.
With bind:
function firstFunction(string) {
return Promise.resolve({string: string, query: 'some result of query'})
}
function secondFunction(otherText, result) {
console.log(result.query) // you have still access to result of query
return result.string + otherText
};
firstFunction('foo').then(secondFunction.bind(null, 'bar')).then(console.log);
With closure
function firstFunction(string) {
return Promise.resolve({string: string, query: 'some result of query'})
}
function secondFunction(text) {
return function(result) {
return result.string + text
}
};
firstFunction('foo').then(secondFunction('bar')).then(console.log);
With anonymous function expression
function firstFunction(string) {
return Promise.resolve({string: string, query: 'some result'})
}
function secondFunction(text, otherText) {
return text.string + otherText
};
firstFunction('foo').then(function(result) {
return secondFunction(result, 'bar')
}).then(console.log);

Mongoose two request depending each other

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.

Mongodb nested query with node js

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();
});
}
});
};

MongoDB dynamic variables in MapReduce

I have node.js router for mongodb mapreduce:
app.get('/api/facets/:collection/:groupby', function(req, res) {
var collection = db.collection(req.params.collection);
var groupby = req.params.groupby;
var map = function() {
if (!this.region) {
return;
}
for (index in this.region) {
emit(this.region[index], 1);
}
}
var reduce = function(previous, current) {
var count = 0;
for (index in current) {
count += current[index];
}
return count;
}
var options = {out: groupby + '_facets'};
collection.mapReduce(map, reduce, options, function (err, collection) {
collection.find(function (err, cursor) {
cursor.toArray(function (err, results) {
res.send(results);
});
})
})
});
This works good. But I want to use my groupby param. When I try to do something like this:
var map = function() {
if (!this[groupby]) {
return;
}
for (index in this[groupby]) {
emit(this[groupby][index], 1);
}
}
I receive TypeError: Cannot call method 'find' of undefined. Is there any way to create such dynamic mapreduce function?
Thanks.
Edited:
Wow! I do it myself. Just pass scope param to mapreduce argument like so scope:{keys: groupby} and then I was able to do var key = this[keys] inside map function and use key variable instead this.region. Great!
Wow! I solved it myself. I just passed a scope param to the mapreduce argument.
scope:{keys: groupby}
Then I was able to do
var key = this[keys]
inside map function and use key variable instead of this.region. Great!

Resources