Looping through x after all i : node.js - javascript

I have this code. I want to loop through all Users in a database and for each user get a list of their Portfolios (which in turn is a collection of stocks):
calculatePortfolios: function(callback) {
var thisuser;
module.exports.getAllUsers(function(err, users) {
/* Loop through all users with i */
for(var i = 0; i < users.length; i++) {
console.log('i = ' + i);
console.log(users.length);
thisuser = users[i]; // Store current user.
/* Loop through all held stocks in current user's portfolio. */
module.exports.getPortfolio(thisuser.username, function(err, portfolio) {
for(var x = 0; x < portfolio.length; x++) {
console.log('x = ' + x);
var total = parseFloat((portfolio[x].held * portfolio[x].unitprice) + thisuser.cash);
console.log(total);
console.log(thisuser.username);
module.exports.updateInvestor(thisuser.username, total, function(err, result) {
console.log(thisuser.username + ' ' + total);
});
}
});
}
callback(err, 'OK');
});
},
The result I get is that all i indices (users) are looped through before all x indices (portfolios). Shouldn't x be an inner loop of i?
Is this something related to how Node.JS works?
Any help is much appreciated. Thank you.

the getPortfolio() is most likely an asynchronous operation, and depending on the internal implementation it may either queue the calls or translate them to http requests for example that may take time, when these requests are completed only then your callback function that operates with x will be called.
But meanwhile getPortfoli() call will return. That is exactly why you see it iterating fast over all your i's, and only then you callback starts getting called.

Lack of JOIN's is really confusing to users who come with SQL background to node.js (event-driven) and NoSQL databases.
Consider this flow for your task:
Load all users.
Iterate through each user and record field which is used for collection relationship (username in your case), usually it is _id.
2b. (Optional) make Object, where key - will be field of relationship, in your case username, and value - will be object it self.
Make another query to related collection with similar query: { _id: { $in: ids } } where ids will be array of user _id's. In your case you need list of usernames.
Iterate through each portfolio item and add it to user object. If 2b did - then no need of two iterations.
Users are done, and have portfolio data and ready to be used.

#PSL: Got it working with async.waterfall. Probably not the most elegant solution, but it works for now.
/**
* #description Calculate the value of an investor's portfolio.
* #function
*/
calculatePortfolios: function(callback) {
async.waterfall([
function(callback) {
var allUsers = [];
module.exports.getAllUsers(function(err, users) {
for(var i = 0; i < users.length; i++) {
allUsers.push(users[i]);
}
});
callback(null, allUsers);
},
function(allUsers, callback) {
var totals = [];
for(var i = 0; i < allUsers.length; i++) {
module.exports.getPortfolio(allUsers[i].username, function(err, portfolio) {
for(var x = 0; x < portfolio.length; x++) {
totals.push(parseFloat(portfolio[x].held * portfolio[x].unitprice));
}
});
}
callback(null, allUsers, totals);
},
function(allUsers, totals, callback) {
for(var i = 0; i < allUsers.length; i++) {
module.exports.updateInvestor
(allUsers[i].username, (totals[i] + allUsers[i].cash), function(err, result) {
console.log('Updated an investor!');
});
}
callback(null, 'OK');
}
]);
callback(null, 'OK');
},

Related

async mongodb call with for loop

I want to update multiple documents so I have to use loop but below code is broken because it has multiple res.json.
for (i = 0; i < dateArray.length; i ++ ) {
Trucks.update({ 'data.date': dateArray[i] }, {'$set': update}, {'multi':true}, function(err,response){
res.json(response);
})
}
I can put res.end() but I want to know when the operation is done.
How can I use async module to improve it? I want to know when the operation is done.
I don't whether it's good solution. It's just a hack.
I think it serves your need. Good luck.
var docsUpdated = 0;
var length = dateArray.length;
for (i = 0; i < length; i++) {
Trucks.update({
'data.date': dateArray[i]
}, {
'$set': update
}, {
'multi': true
}, function(err, response) {
if(err) res.end();
docsUpdated += response;
if( i === length-1){
res.json(docsUpdated);
}
})
}

How can I execute a statement AFTER a loop finishes in javascript?

I'm querying a mongo database to retrieve the tiles for the display in rougelike game. This is the function I use:
function get_display(){
var collections = ['austinsroom'];
var db = mongojs(uri, collections);
var temphtml = '';
for(var j = 0; j < 3; j++) {
console.log("y=" + String(j));
db.austinsroom.find({"y": j}, {}).sort({"x": 1}, function(err, records) {
if(err) {
console.log("There was an error executing the database query.");
return;
}
var i = records.length;
while(i--) {
temphtml += records[i].display;
}
temphtml += '<br>';
//console.log(temphtml);
//return temphtml;
//THE ONLY WAY I CAN GET ANYTHING TO PRINT IN THE CONSOLE IS IF I PUT IT INSIDE THE LOOP HERE
});
//console.log(temphtml);
//return temphtml;
//THIS DOES NOTHING
}
//console.log(temphtml);
//return temphtml;
//THIS DOES NOTHING
}
get_display();
If I put the console.log(temphtml) inside the loop, it prints out three times which isn't what I want. I only want the final string (i.e. ...<br>...<br>...<br>. Also I can't ultimately return the temphtml string, which is actually the important thing. Is this some quirk of javascript? Why would it not execute statements after the loop?
Also: is there a better way to retrieve every element of a grid that's stored in a mongo database, in order, so it can be displayed properly? Here's what the mongo documents look like:
{
"_id": {"$oid": "570a8ab0e4b050965a586957"},
"x": 0,
"y": 0,
"display": "."
}
Right now, the game is supposed to display a "." in all empty spaces using the x and y values for the coordinates. The database is indexed by "x" values.
See async.whilst. You want flow control of the for loop, for which this provides a callback to control each loop iteration.
var temphtml = "",
j = 0;
async.whilst(
function() { return j < 3 },
function(callback) {
db.austinsroom.find({"y": j }, {}).sort({"x": 1}, function(err, records)
temphtml += records.map(function(el) {
return el.display;
}).join("") + '<br>';
j++;
callback(err);
});
},
function(err) {
if (err) throw err;
console.log(temphtml);
}
)
Either that or use Promise.all() on collected promises to return "one big result". But you would also need to switch to promised-mongo from mongojs, as the nearest equivalent, since there are more mongodb drivers that actually support promises. That one is just the direct fork from mongojs:
var temphtml = "",
j = 0,
promises = [];
for ( var j=0; j < 3; j++ ) {
promises.push(db.austinsroom.find({"y": j }, {}).sort({"x": 1}).toArray());
promises.push('<br>'); // this will just join in the output
)
Promise.all(promises).then(function(records) {
temphtml += records.map(function(el) {
return el.display;
}).join("");
})
Not exactly the same thing, since it's one list output and not three, but the point is that the Promise objects defer until actually called to resolve, so you can feed the paramters in the loop, but execute later.
I do not use MongoDB but from what I am reading it is asynchronous. So what is happening is your db.austinsroom.find call fires another "thread" and returns to the for loop to continue the next iteration.
One way to do what you want is have a check at the end of your db.austinsroom.find function to see if you're done with the for loop. Something like:
function get_display()
{
var collections = ['austinsroom'];
var db = mongojs(uri, collections);
var temphtml = '';
var doneCounter = 0;
for(var j = 0; j < 3; j++)
{
console.log("y = " + String(j));
db.austinsroom.find({"y": j}, {}).sort({"x": 1}, function(err, records)
{
if(err)
{
console.log("There was an error executing the database query.");
return;
}
var i = records.length;
while(i--)
{
temphtml += records[i].display;
}
temphtml += '<br>';
// we're done with this find so add to the done counter
doneCounter++;
// at the end, check if the for loop is done
if(doneCounter == 3)
{
// done with all three DB calls
console.log(temphtml);
}
});
}
}
This is probably not the best way as I know nothing about MongoDB but it should put you on the right path.

Where to put the callback?

db.listCollections().toArray(function(err, collections){
for(i = 1; i < collections.length; i++){
if(i < parseInt(collections.length)){
var collect = db.collection(collections[i].name.toString(),function(){
collect.count(function(err,result){
console.log(collections[i].name.toString());
corrCount = corrCount + result;
});
});
}else{
collect = collections[i].name;
}
}
});
So my problem is that collect ends up being undefined and thus one can't count the amount of entries in the collection. Something tells me that I should solve this with callback but I keep failing. Also I don't understand why the console prints out 2 times before it shots of the error. I'm using nodejs with mongodb native driver.
You need to move the rest of the code out of the callback, like so:
db.listCollections().toArray(function(err, collections) {
for (i = 1; i < collections.length; i++) {
if (i < parseInt(collections.length)) {
var collect = db.collection(collections[i].name.toString());
collect.count(function(err, result) {
console.log(collections[i].name.toString());
corrCount = corrCount + result;
});
} else {
collect = collections[i].name;
}
}
});

Parse Cloud Code Generate List based on a Review (object)

I am struggling with Parse Cloud code. I am trying to create a list of breweries based on a user reviews. I think I would need to calculate the average review for each brewery and then use this value to generate the list. I want to only include breweries with an average of 4 or higher.
Please forgive my ignorance on the matter, thanks in advance!
This is what I have and it doesn't work.
Parse.Cloud.afterSave("reviewAvergae", function(request){
var Review = Parse.Object.extend("Reviews");
var query = new Parse.Query("Reviews");
query.get(request.object.get("rating").id, {
success: function(reviews) {
var sum = 0;
for (var i = 0; i < reviews; i++) {
sum += reviews[i].get("rating");
}
var ave = sum / reviews.length;
reviews.set("average", ave);
reviews.save();
}
});
/*
query.equalTo("breweryId", request.params.brewId);
query.find({
success: function(result) {
var sum = 0;
for (var i = 0; i < result.length; i++) {
sum += result[i].get("rating");
}
Review.set("averageRating", sum / result.length);
Review.save();
console.log(sum / result.length);
}
});
*/
});
Parse.Cloud.define("featured", function(request, response) {
var query = new Parse.Query("Reviews");
// query.near("location", request.params.loc);
query.withinMiles("location", request.params.loc, 50);
query.limit(10);
query.find({
success: function(results) {
var sum = 0;
var rating;
var ratings = new Array();
var id;
var ids = new Array();
for (var i = 0; i < results.length; i++) {
id = results[i].get("breweryId");
ids.push(id);
}
response.success(results);
console.log(ratings.length);
},
error: function() {
response.error("No breweriers in your area have been reviewed.");
}
});
});
On parse my Review Object looks like this:
Note: My actual app is for iOS and written in swift.
Update: I have tried the following after adding a brewery object. I now get the following error : [Error]: function not found (Code: 141, Version: 1.7.5)
Parse.Cloud.afterSave("Reviews", function(request){
var Review = Parse.Object.extend("Reviews");
var breweryId = Review.get("breweryId");
var query = new Parse.Query("Reviews");
query.equalTo("breweryId", breweryId);
query.find({
success: function(results) {
var total = 0;
for (var i = 0; i < results.length; i++) {
var object = results[i];
total += object.get("rating");
}
var averageRating = total / results.length;
console.log(averageRating);
var query = new Parse.Query("Brewery");
query.equalTo("breweryId", breweryId);
}, error: function() {
}
});
});
here are some updated images of my my Objects in Parse
Understand from the comments that there's an external system with Brewery objects. In order to do parse.com computations about breweries, it will be necessary to represent them internal to parse, even if it is basically a pointer to an object in another system.
So, add a "Brewery" object. It will get an objectId from parse, but you should add a "breweryId" string attribute, as you have with your Review class. It should also have an "averageRating" number attribute.
Now we need logic to compute and save the average rating, given a breweryId (that's the external id, not the parse objectId)...
// a very helpful toolkit for dealing with js, especially arrays
var _ = require('underscore');
function updateAverageRatingForBreweryWithId(breweryId) {
var average;
// get all of the reviews for this breweryId
var query = new Parse.Query("Review");
query.equalTo("breweryId", breweryId);
return query.find().then(function(reviews) {
// pull out each review's rating attribute
var ratings = _.map(reviews, function(review) { return review.get("rating"); });
// this is just an average computation, summing the ratings and dividing by their count
average = _.reduce(ratings, function(memo, num) { return memo + num; }, 0) / (ratings.length === 0 ? 1 : ratings.length);
// go get the parse.com Brewery object for this breweryId
// we will save the average rating there
var query = new Parse.Query("Brewery");
query.equalTo("breweryId", breweryId);
return query.first();
}).then(function(brewery) {
brewery.set("averageRating", average);
return brewery.save();
});
}
This function takes an external breweryId as input, gets the reviews for that brewery, computes the average, then finds (your new) Brewery object with that breweryId sets its averageRating and saves it.
A good place to call this is after saving a Review, which is what it looked like you were up to in the original post. That afterSave must be named (first parameter) "Review" if you want it to work on the Review object (not the name you gave it or "Comment", which #RyanKreager mentions for some reason)...
Parse.Cloud.afterSave("Review", function(request) {
var review = request.object;
var breweryId = review.get("breweryId");
updateAverageRatingForBreweryWithId(breweryId);
});
Now with a Brewery object we're in a position to query it with the real goal: what are the highly rated breweries near me? I can add some additional advice for that, but it only makes sense if you create a Brewery object.
I see a couple of errors:
You should be storing the average field on the Brewery object, not the review. That way you just update one object and you can do a nice, tidy query for all the Brewery objects where average > x etc.
breweryId is a string. This should be a pointer to the Brewery object. Also change the name to just brewery
Once you have those two things cleared up, something like this makes more sense for the afterSave on a Review object:
Your afterSave needed some adjustments. Try this:
Parse.Cloud.afterSave("Comment", function(request) {
var Review = Parse.Object.extend("Review");
var query = new Parse.Query(Review);
query.equalTo("brewery", request.object.get("brewery"));
query.find({
success: function(results) {
// Find the average from all the brewery reviews
var total = 0;
for (var i = 0; i < results.length; i++) {
var object = results[i];
total += object.get("rating");
}
var averageRating = total / results.length;
var updatedObjects = [];
for (var i = 0; i < results.length; i++) {
var object = results[i];
object.set("average", averageRating);
updatedObjects.push(object);
}
Parse.Object.saveAll(updatedObjects);
}, error: function(error) {
alert("Error: " + error.code + " " + error.message);
});
});

Executing code after calling dynamic number of functions

I'm only crawling in JS so probably the solution is obvious.
I'm writing a Chrome extension which in browser action (after clicking on extension's button) reads several pages and from each of them it retrieves an integer. Then, it creates a table with these integers. I'm doing it qith AJAX so it's asynchronous and I want to have all integers before creating the table.
I've read these topics:
Pass in an array of Deferreds to $.when()
How to tell when multiple functions have completed with jQuery deferred
...and wrote a code that doesn't work.
var sum = 0;
document.addEventListener('DOMContentLoaded', function () {
var deferreds = [];
var users = [...], pages = [...];
for(i = 0; i < users.length; ++i) {
for(j = 0; j < pages.length; ++j)
deferreds.push(window[pages[j]](users[i], pages[j]));
}
$.when.apply(null, deferreds).done(function() {
alert("sum: " + sum);
});
});
function Name(user, page) {
$.get("http://page2/" + user, function(data) {
sum += 7;
});
return 7;
}
function Name2(user, page) {
$.get("http://page/" + user, function(data) {
sum += 84;
});
return 84;
}
So the alert prints 0 instead of 91. I cut the part with table as all fields are "undefined" anyway. If I get sum = 91, I'll probably get the table.
As I said - obvious mistake. The functions should look like:
function Name(user, page) {
return $.get("http://page2/" + user, function(data) {
sum += 7;
});
}

Categories

Resources