node: res.send-ing an array not working - javascript

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

Related

How to troubleshoot response when using Query inside a For statement

I'm currently writing an API with a query that takes input as an array and then deletes it in turn via a for statement.
However, if it is not a normal request (ex) result.affectedRows === 0) the DB will generate an UnhandledPromiseRejectionWarning: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client.
How can I solve this?
API CODE
exports.delete = (req, res, next) => {
const arr = req.body.arr
for (let i = 0; i < arr.length; i++) {
let sql = `DELETE FROM post WHERE postId = ?`;
Post.query(sql, [arr[i]], (err, result) => {
if (err) {
return next(err);
}
if (result.affectedRows === 0) {
res.send('Delete failed');
return;
}
});
}
res.send('Successfuly Delete');
};
That error is caused by your code sending multiple http responses, i.e. calling res.send more than once. This happens in your code because return is only breaking out of the callback function passed to Post.query, so the last res.send in the exports.delete function still gets called, causing the error.
You can restructure your code to something like this to avoid sending multiple responses:
function runQuery (sql, data) {
return new Promise((resolve, reject) => {
Post.query(sql, [data], (err, result) => {
if (err) {
reject(err);
} else if (result.affectedRows === 0) {
reject('Delete failed');
} else {
resolve();
}
});
})
}
exports.delete = (req, res, next) => {
const arr = req.body.arr
let sql = `DELETE FROM post WHERE postId = ?`;
let deletes = [];
for (let i = 0; i < arr.length; i++) {
deletes.push(runQuery(sql, arr[i]));
}
Promise.all(deletes).then(() => {
res.send('Successfuly Delete');
}).catch((err) => {
if(err === 'Delete failed') {
res.send(err);
} else {
next(err);
}
})
};

Object context lost inside of function

router.get("/", function(req, res, next) {
axios.get('https://www.behance.net/v2/users/user/projects/4889175?api_key=' + 'API')
.then(function(response) {
var data = response.data.projects;
for(var i=0;i < data.length; i++) {
Behance.findOne({ name: data[i].name }, function(err, user) {
if (err) { return next(err); }
if (!user) {
console.log(this.name);
// var newBehance = new Behance({
// name: this.name,
// });
// newBehance.save(next);
}
});
}
})
.catch(function(error) {
console.log(error);
});
});
I'm pulling projects from Behance w/ their API and trying to save each one to a DB if it doesn't already exist using findOne. Inside of the for loop data[i].name returns a value, but inside of the findOne function it returns undefined.
I can't seem to figure out why this is happening..
I think the problem is that you're logging out this.name instead of data[i].name. As far as I can tell, this is window.
You can use .forEach to simplify things a bit:
router.get("/", function(req, res, next) {
axios.get('https://www.behance.net/v2/users/user/projects/4889175?api_key=' + 'API')
.then(function(response) {
response.data.projects.forEach(({name}) => {
Behance.findOne({ name }, function(err, user) {
if (err) { return next(err); }
if (!user) {
console.log(name);
// var newBehance = new Behance({
// name,
// });
// newBehance.save(next);
}
});
}
})
.catch(function(error) {
console.log(error);
});
});

I can't stop the asynchronous

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.

Push data to out side async function

I still do not handle well the asynchronous functions, I have an array of items, and I'm trying to for each item calculate some values and push to another array outside of the async function. Then I want to make some statistics calculation and send to front end. It's server side, nodejs handler, my code:
exports.register = function (plugin, options, next) {
function isInArray(value, array) {
return array.indexOf(value) > -1;
}
function statistics(values) {
var sum = math.sum(values);
var max = math.max(values);
var min = math.min(values);
var stddev = math.std(values);
var mean = math.mean(values);
var count = values.length;
}
plugin.route({
method: 'GET',
path: '/statistics/{orgId}/layout/{layoutId}',
config: {
pre: [
authorize(hasRole(['OPERATIONAL', 'STRATEGIC', 'LOP', 'TACTICAL']))
],
handler: function (request, reply) {
Category.find()
.where('organization')
.equals(request.params.orgId)
.exec(function (err, categories) {
var weight = [];
var price = [];
var volume = [];
var thisAR = [];
if (err || categories === null) {
return reply(Boom.badRequest('Categoria inexistente'));
} else {
Location.findById(request.params.layoutId)
.exec(function (err, layout) {
if(err) {
console.log(err);
}
var searchItems = function searchItems(category, next) {
Item.find()
.where('category')
.equals(category._id)
.exec(function (err, items) {
if (err) {
console.log(err);
} else {
var valuesToCalculate = [];
var itemsFiltered = [];
_.forEach(items, function(item) {
if(item.location && item.location !== null) {
if(isInArray(item.location.toString(), layout.contents)) {
itemsFiltered.push(item);
}
}
});
valuesToCalculate.push(itemsFiltered.length * category.data.weight);
valuesToCalculate.push(itemsFiltered.length * category.data.price);
valuesToCalculate.push(itemsFiltered.length * category.data.volume);
next(valuesToCalculate);
}
});
}
var onFinish = function onFinish(value, err) {
if(err) {
console.log(err);
}
console.log(value);
thisAR.push.apply(value);
}
async.each(categories, searchItems, onFinish);
console.log(thisAR);
//var arrays = [statistics(weight), statistics(price), statistics(volume)];
//return arrays;
});
}
});
}
}
});
next();
};
A few things stand out to me about this. First, you only call reply if there is an err or categories is null. Also, you are attempting to pass a non null value to an async.each callback. According to this: https://github.com/caolan/async#eacharr-iterator-callback, "if no error has occurred, the callback should be run without arguments or with an explicit null argument". I think you may misunderstand how the onFinish callback works with async.each.. it is not called for each item, it is called when all of the iterator functions have completed. So, rather than pushing items onto thisAR in onFinish, you should do so inside searchItems. I think this should work:
exports.register = function (plugin, options, next) {
function isInArray(value, array) {
return array.indexOf(value) > -1;
}
function statistics(values) {
var sum = math.sum(values);
var max = math.max(values);
var min = math.min(values);
var stddev = math.std(values);
var mean = math.mean(values);
var count = values.length;
}
plugin.route({
method: 'GET',
path: '/statistics/{orgId}/layout/{layoutId}',
config: {
pre: [
authorize(hasRole(['OPERATIONAL', 'STRATEGIC', 'LOP', 'TACTICAL']))
],
handler: function (request, reply) {
Category.find()
.where('organization')
.equals(request.params.orgId)
.exec(function (err, categories) {
var weight = [];
var price = [];
var volume = [];
var thisAR = [];
if (err || categories === null) {
return reply(Boom.badRequest('Categoria inexistente'));
} else {
Location.findById(request.params.layoutId)
.exec(function (err, layout) {
if(err) {
console.log(err);
}
var searchItems = function searchItems(category, next) {
Item.find()
.where('category')
.equals(category._id)
.exec(function (err, items) {
if (err) {
console.log(err);
} else {
var valuesToCalculate = [];
var itemsFiltered = [];
_.forEach(items, function(item) {
if(item.location && item.location !== null) {
if(isInArray(item.location.toString(), layout.contents)) {
itemsFiltered.push(item);
}
}
});
valuesToCalculate.push(itemsFiltered.length * category.data.weight);
valuesToCalculate.push(itemsFiltered.length * category.data.price);
valuesToCalculate.push(itemsFiltered.length * category.data.volume);
thisAR.push.apply(valuesToCalculate);
}
next(err);
});
}
var onFinish = function onFinish(err) {
if(err) {
console.log(err);
}
console.log(thisAR);
// call reply here
}
async.each(categories, searchItems, onFinish);
console.log(thisAR);
//var arrays = [statistics(weight), statistics(price), statistics(volume)];
//return arrays;
});
}
});
}
}
});
next();
};
I can't comment since I'm new but it seems that just removing the .apply in thisAR.push.apply(valuesToCalculate); will return a full array to your console.log.

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