I can't stop the asynchronous - javascript

So I am trying to get an array with a loop and the asynchronous nature of nodejs is killing me. Here is my code:
getDevices(userIDs, function(result) {
if (result) {
sendNotification(messageUser, messageText, result);
res.send("Success");
} else {
res.send("ERROR");
}
});
});
function getDevices(userIDs, callback) {
var userDevices = [];
var device = [];
for (var i = 0; i < userIDs.length; i++) {
searchRegisterDevices(userIDs[i], function(result) {
if (result) {
for (var j = 0; j < result.length; j++) {
device = {platform: result[j].platform, token: result[j].token};
userDevices.push(device);
}
} else {
console.log("ERROR");
}
});
}
callback(userDevices);
}
function searchRegisterDevices(userID, callback) {
MongoClient.connect(url, function(err, db) {
if (err) {
console.log(err);
} else {
console.log("We are connected");
}
var collection = db.collection('RegisteredDevices');
collection.find({userID: userID}).toArray(function (err, result) {
if (err) {
console.log("Error: " + err);
} else if (result.length) {
callback(result);
} else {
console.log('No document found');
}
db.close();
});
});
I first need to get all my devices out of my mongodb collection that match the ID in userIDs. SO userIDs is an array of IDs that are tied to devices in the collection. Once I get the device I need to get the device token out of the returned object.
So:
1) call getDevices passing an array of userIDs
2) call searchRegisterDevices with a device ID.
3) searchRegisterDevices returns an array of devices.
4) get the device token/s out of that array and push to userDevices array.
5) return userDevices array
6) call sendNotification with the array of userDevices
I know my issues, I just am having a hard time solving them

Instead of getting user device for each user you should get them using single query:
First: It will reduce number of calls
Second: It will save you to handle callbacks o/p.
For it use $in operator.
Change searchdevices method:
function searchRegisterDevices(userID, callback) {
MongoClient.connect(url, function(err, db) {
if (err) {
console.log(err);
} else {
console.log("We are connected");
}
var collection = db.collection('RegisteredDevices');
collection.find({
userID: {
$in: userIDs
}).toArray(function(err, result) {
if (err) {
console.log("Error: " + err);
} else if (result.length) {
callback(result);
} else {
console.log('No document found');
}
db.close();
});
});
}
It will return array of userdevices for passed userids.

Related

Variable inaccessible inside callback function

I have a variable deleteFlag which is inaccessible inside a function even though the variable's scope is global.
Explanation (Pls refer my code simultaneously):
Here, I am trying to get a MongoDB collection details, the collection store a date document (result[i].date). The variable difResult stores the difference between the current date and the date fetched from MongoDB. And let's say if the value of difResult is more than a specific threshold then handle respective if-else conditions.
My if block i.e. if(difResult>20000) has a child-process, exec function and a callback function to delete MongoDB collection, now in this function I am trying to access var deleteFlag which is sort inaccessible.
Why? And how can I make is accessible inside my function?
app.js
MongoClient.connect("mongodb://localhost:27017/", {
useUnifiedTopology: true
}, function(err, db) {
if (err) throw err;
var dbo = db.db("dbName");
dbo.collection("colName").find({}).toArray(function(err, result) {
if (err) throw err;
for (var i = 0; i < result.length; i++) {
var difResult = Math.round((today - result[i].date));
var deleteFlag = result[i].date; // Declared here and should be accessbile within the function
console.log("Delete Flag " + deleteFlag.toISOString()); //Show correct value here
console.log("Result Date " + result[i].date);
if (difResult > 20000) {
var result2 = cp.exec("rm -rf /path/" + deleteFlag.toISOString(), function(error, stdout, stderr) {
if (error !== null) {
console.log('exec error: ' + error);
return res1.status(500).json({
error: "Failed!"
});
} else {
MongoClient.connect("mongodb://localhost:27017/", {
useUnifiedTopology: true
}, function(err, db) {
console.log("Delete Flag From Collection ", +deleteFlag.toISOString());
//The above console log gives NaN or null value
//Suggest that var deleteFlag is not accessible inside this callback function
if (err) throw err;
var dbo = db.db("dbName");
var myquery = {
date: deleteFlag
};
dbo.collection("colName").deleteOne(myquery, function(err, obj) {
if (err) throw err;
console.log("1 document deleted");
db.close();
});
});
}
});
} else {
console.log("Else msg");
}
}
db.close();
});
});
You don't have to call the database twice you can optimize your code and use it like this
MongoClient.connect("mongodb://localhost:27017/", {
useUnifiedTopology: true
}, function(err, db) {
if (err) throw err;
var dbo = db.db("dbName");
dbo.collection("colName").find({}).toArray(function(err, result) {
if (err) throw err;
for (var i = 0; i < result.length; i++) {
var difResult = Math.round((today - result[i].date));
var deleteFlag = result[i].date; // Declared here and should be accessbile within the function
console.log("Delete Flag " + deleteFlag.toISOString()); //Show correct value here
console.log("Result Date " + result[i].date);
if (difResult > 20000) {
var result2 = cp.exec("rm -rf /path/" + deleteFlag.toISOString(), function(error, stdout, stderr) {
if (error !== null) {
console.log('exec error: ' + error);
return res1.status(500).json({
error: "Failed!"
});
} else {
var myquery = {
date: deleteFlag
};
dbo.collection("colName").deleteOne(myquery, function(err, obj) {
if (err) throw err;
console.log("1 document deleted");
});
}
});
} else {
console.log("Else msg");
}
}
db.close();
});
});
However if for whatever reason you need to call the database twice then store deleteFlag values in an array and then access the array wherever you like

node: res.send-ing an array not working

My User model has a field of type Array with values like this
"courseId" : [
"5ac1fe64cfdda22c9c27f264",
"5ac207d5794f2910a04cc9fa",
"5ac207d5794f2910a04cc9fa"
]
My routes are configured in this way:
router.get('/:userid/vendor-dashboard', function (req, res) {
var courseID = [];
User.findById(req.params.userid, function (err, doc) {
if (err) {
res.send(err);
} else {
for (var i = 0; i < doc.courseId.length; i++) {
Course.findById(doc.courseId[i], function (err, course) {
if (err) {
console.log(err);
} else {
console.log(course.title);
courseID.push(course.title);
}
})
}
res.send(JSON.stringify(courseID));
}
})
})
First I'm finding a user and when user is found, He should find all the courses in the array and display their title.
Now, I'm able to get the title in console, but when I try to send it through res.send, it shows an empty array.
What am I doing wrong?
The main problem is that you are sending the response before getting the response from Course model.
The correct way using callback will be:
router.get('/:userid/vendor-dashboard', function(req, res) {
var courseID = [];
User.findById(req.params.userid, function(err, doc) {
if (err) {
return res.send(err);
}
var resolved = 0;
for (var i = 0; i < doc.courseId.length; i++) {
Course.findById(doc.courseId[i], function(err, course) {
if (err) {
return console.log(err);
}
courseID.push(course.title);
if (++resolved === doc.courseId.length) {
res.send(JSON.stringify(courseID));
}
})
}
})
})

Run loop on the server with node.js

Let's say i need to constantly collecting some data from a lot of clients and in parallel running some complex loop that solving some stuff with this data. How can i do it? Should i just write this in my piece of code:
app.get('/', function(req, res) {
res.sendFile(__dirname + '/public/views/index0.html');
});
io.sockets.on('connection', function(socket) {
// SOME STUFF WITH THE SOCKET
socket.on('disconnect', function(data) {
//SOME OTHER STUFF
});
});
while(...) {
//THE LOOP STUFF
}
Or i need to use the setTimeout() and setInterval() functions? How can i do the loop on the server that runs in parallel with the callbacks' stuff?
Don’t use while for make it, this block a thread. setTimeout() will run only once. You need to use setInterval() function.
You can use the async module to handle the async operation with the callback or use promise to avoid callback.
Here how I handle a complex async for each operation, that might helpfull to you get idea handeling ayncs forach
var cond = { _schedule: schedule_id }; // find curse by schedule id
Course.find(cond, function (err, courses) {
if (err) {
callback({ "success": false, "message": "Not able to update" });
} else {
async.forEachLimit(courses, 1, function (course, coursesCallback) {
async.waterfall([
function (callback) {
var schedule_date = moment(change_data.date).format('YYYY-MM-DD') + "T00:00:00.000Z"
var Assignmentcond = {
assignment_schedule_order: {
$gte: schedule_date
},
_course: course._id,
_schedule: schedule_id,
_user: userid
};
Assignment.find(Assignmentcond)
.populate({
path: '_course',
})
.lean()
.sort({ assignment_schedule_order: 1 })
.exec(function (err, AssignmentList) {
if (err) {
callback(null, '');
} else {
//console.log("------------------AssignmentList---------------------------");
//console.log(AssignmentList);
async.forEachLimit(AssignmentList, 1, function (ThisAssignmentCell, ThisAssignmentCellCallback) {
async.waterfall([
function (callback) {
var SearchObj = items;
var lebelObject = {};
for (var i = 0, flag = 0, insert = 0; i < SearchObj.length; i++) {
if (SearchObj[i].date == ThisAssignmentCell.assignment_schedule_date) {
flag = 1;
}
if (flag == 1 && SearchObj[i].label != "") {
if (ThisAssignmentCell.day == SearchObj[i].day_index) {
insert = 1;
var lebelObject = SearchObj[i];
break;
}
}
}
callback(null, ThisAssignmentCell, lebelObject, insert);
},
function (ThisAssignmentCell, SearchObj, insert, callback) {
console.log('----------------------');
console.log('ThisAssignmentCell', ThisAssignmentCell);
console.log('SearchObj', SearchObj);
console.log('----------------------');
if (insert > 0) {
var query = { _id: ThisAssignmentCell._id },
fields = {
assignment_date: moment(SearchObj.date).format('MM/DD/YYYY'),
assignment_schedule_date: moment(SearchObj.date).format('YYYY-MM-DD'),
assignment_schedule_order: new Date(SearchObj.date),
day: SearchObj.day_index,
dayNum: SearchObj.weekday_num
},
options = { upsert: false };
Assignment.update(query, fields, options, function (err, affected) {
callback(null, '');
});
} else {
// var cond = { _id: ThisAssignmentCell._id};
// Assignment.remove(cond)
// .exec(function (err, cnt) {
// callback(null, '');
// });
}
}
], function (err, result) {
// result now equals 'done'
console.log('done')
ThisAssignmentCellCallback();
});
}, function (err) {
console.log("Assignment For Loop Completed");
callback(null, AssignmentList);
});
}
});
}
], function (err, result) {
// result now equals 'done'
console.log('done')
coursesCallback();
});
}, function (err) {
console.log("courses For Loop Completed");
});
}
});

MongoDB get an item out of db

So i have implemented a mongodb on my nodejs server. And what I have done is store users via:
function insertUser() {
var collection = dbb.collection('user');
var user1 = {name: user, token: token};
collection.insert(user1, function(err, result) {
if (err) {
console.log(err);
} else {
console.log(result);
}
});
}
function findUserByName(devName) {
var collection = dbb.collection('user');
collection.find({name: devName}).toArray(function (err, result) {
if (err) {
console.log(err);
} else if (result.length) {
console.log('Found: ', result);
selectedUserToken = result.token;
} else {
console.log('No document found');
insertUser();
}
dbb.close();
});
}
So result will equal:
Found: [ { _id: 57be1cadc281c03ea116c9ab,
name: 'Austin Hunter',
token: 'dJyXVjMJk08kXWrua8SUjKb....SxACihKZoR53y_wOZmcFNKMmD5q99QNvsp3flL' } ]
My question is, how can I get that token out to equal selectedUserToken so I can send a push notification with gcm? Right now result.token is undefined.
You should use findOne() instead of find() since you only expect a single result back:
function findUserByName(devName) {
var collection = dbb.collection('user');
collection.findOne({name: devName}, function (err, result) {
if (err) {
console.log(err);
} else if (result) {
console.log('Found: ', result);
selectedUserToken = result.token;
} else {
console.log('No document found');
insertUser();
}
dbb.close();
});
}
But if you wanted to leave your code as is with the find() you would just retrieve the first element of the resulting array retrieved by find()
function findUserByName(devName) {
var collection = dbb.collection('user');
collection.find({name: devName}).toArray(function (err, result) {
if (err) {
console.log(err);
} else if (result.length) {
console.log('Found: ', result);
selectedUserToken = result[0].token;
} else {
console.log('No document found');
insertUser();
}
dbb.close();
});
}
Maybe result[0].token, because result is an array of user items.

For loop in async function and timing issue

I am querying my mongodb for the user's email that is being passed through a session. When that email is found it looks for that user's friends, and those friends are supposed to be passed to the usersFriends array which is then sent in the chunk to the browser. I included all the code in this block even though the transaction block isn't really pertinent or so I think.
The Problem: is that the usersFriends array is outputting an empty array everywhere except when the console.log is inside the for loop. Thoughts?
app.get('/api/chunk', function(req, res){
var last5;
var usersFriends = [];
Transaction.find().sort({$natural:-1}).limit(5).exec(function(err, docs){
if (err) {
console.log(err);
} else {
last5 = docs;
}
});
User.findOne({ email: req.user.email }, function(err, user){
if (!user) {
console.log(err);
} else {
for (var i = 0; i < user.friends.length; i++) {
(function(cntr){
User.findOne({ email: user.friends[cntr].email}, function(err, result) {
result.password = "Sneaky sneaky"
var name = result.firstName + " " + result.lastName;
usersFriends.push({
name: name,
index: cntr
});
});
})(i);
var chunk = {
"friends": usersFriends,
"transactions": last5
};
} }console.log(usersFriends); // empty array
});
});
Combining all the various things we've discussed in comments for this list of changes:
Pass the cntr in an IIFE so it is uniquely captured for each separate .findOne() request.
In each response, check if this is the last one done so we know that all results have arrived.
Start the .findOne() operations from the completion of the Transaction.find() operation (since I don't know that operation, I'm guessing how this particular aspect should be implemented, but you should be able to see the general idea).
Though would result in this code:
app.get('/api/chunk', function(req, res) {
var last5;
var usersFriends = [];
Transaction.find().sort({
$natural: -1
}).limit(5).exec(function(err, docs) {
if (err) {
console.log(err);
} else {
last5 = docs;
User.findOne({
email: req.user.email
}, function(err, user) {
if (!user) {
console.log(err);
} else {
var totalCnt = user.friends.length;
for (var i = 0; i < totalCnt; i++) {
(function(cntr) {
User.findOne({
email: user.friends[cntr].email
}, function(err, result) {
result.password = "Sneaky sneaky"
var name = result.firstName + " " + result.lastName;
usersFriends.push({
name: name,
index: cntr
});
if (usersFriends.length === totalCnt) {
// all results are done here
// you can create the final response
var chunk = {
"friends": usersFriends,
"transactions": last5
};
}
});
})(i);
}
}
});
}
});
});

Categories