So I have this code:
Rating.find({user: b}, function(err,rating) {
var covariance=0;
var standardU=0;
var standardV=0;
while (rating.length>0){
console.log("the avarage rating u is:" + avarageRatingU)
console.log("the avarage rating v is:" + avarageRatingV)
currentMovie = rating.pop();
var u=currentMovie.value-avarageRatingU;
standardU = standardU + Math.pow(u,2);
var v=0;
Rating.find({movieid:currentMovie.movieid, user:a}, function(err,ratings) {
if (err) throw err;
if(ratings.length>0){
v=ratings.pop().value-avarageRatingV;
standardV=standardV+Math.pow(v,2);
covariance =covariance+u*v;
console.log(covariance);
}
})
}
console.log(covariance)
callback(null,covariance);
//sim = covariance/(Math.sqrt(standardU)*Math.sqrt(standardV));
})
The problem is when I print covariance, it will print 0, because the print happends before the calculation. I thought about using async.series, however, I can not have the callback function inside the while loop.
Any tips would be appreciated.
Ty :)
Can't you call your callback inside the loop somehow like this? Not sure if this helps you.
while (rating.length>0){
console.log("the avarage rating u is:" + avarageRatingU)
console.log("the avarage rating v is:" + avarageRatingV)
currentMovie = rating.pop();
var u=currentMovie.value-avarageRatingU;
standardU = standardU + Math.pow(u,2);
var v=0;
Rating.find({movieid:currentMovie.movieid, user:a}, function(err,ratings) {
if (err) throw err;
if(ratings.length>0){
v=ratings.pop().value-avarageRatingV;
standardV=standardV+Math.pow(v,2);
covariance =covariance+u*v;
console.log(covariance);
}
if(rating.length == 0){
console.log(covariance);
callback(null, covariance);
}
})
}
Using either promises or the async library would be the way to go in this case instead of the traditional while loop. Since you are already familiar with async, fetch all the data you need ahead of time with async.map (https://github.com/caolan/async#map).
Rating.find({user: b}, function(err,rating) {
var covariance=0;
var standardU=0;
var standardV=0;
aync.map(rating, function(currentMovie, cb) {
Rating.find({ movieid:currentMovie.movieid, user:a }, function(err,ratings) {
cb(err, ratings);
});
}, function(err, results) {
if (!err) {
// compute covariance with all movie data
}
});
})
This is my second promises solution tonight :). So, let me try:
//supposing you are using node js
var Q = require('q'); //https://github.com/dscape/nano
Rating.find({user: b}, function(err,rating) {
var covariance=0;
var standardU=0;
var standardV=0;
var promises = [];
while (rating.length>0){
console.log("the avarage rating u is:" + avarageRatingU)
console.log("the avarage rating v is:" + avarageRatingV)
currentMovie = rating.pop();
var u=currentMovie.value-avarageRatingU;
standardU = standardU + Math.pow(u,2);
var v=0;
var def = Q.defer();
promises.push(def);
Rating.find({movieid:currentMovie.movieid, user:a}, function(err,ratings) {
if (err) {
def.reject();
throw err;
}
if(ratings.length>0){
v=ratings.pop().value-avarageRatingV;
standardV=standardV+Math.pow(v,2);
covariance =covariance+u*v;
def.resolve();
//console.log(covariance);
}
});
}
Q.allSettled(promises, function() {
console.log(covariance);
});
callback(null,covariance);
//sim = covariance/(Math.sqrt(standardU)*Math.sqrt(standardV));
});
Try using a Promise! Now that Node.js 4.x is out, all of JavaScript ES6 (and Promises) comes with it. You can look at the Mozilla Foundation documentation for promises here.
If you've never used a promise before, all it really does is allow you to return a value before the value is available. Later, the promise is able to become resolved or rejected with a real value.
In this case, you're going to want to encapsulate your inner Rating.find() call inside a new Promise( function(resolve, reject) { ... } ) and attach a then() clause onto your promise which prints out the value of the covariance. When you retrieve the real covariance from your inner Rating.find() function, simply use the resolve parameter (which is a function which takes a single value) and pass the covariance so it gets passed into the function in your Promise's then() clause, which will print the covariance.
Shoot me a comment if there's any confusion!
Assuming that rating (or ratings) is an array you need to iterate over and call an async function on each item (e.g. Rating.find), async.eachSeries would probably make the most sense.
async.eachSeries(ratings, function iterator(rating, callback) {
// invoke async function here for each rating item
// and perform calculations
}, function done() {
// all async functions have completed, print your result here
});
More documentation here: https://github.com/caolan/async
This is a standard problem of calling an asynchronous function within a loop.
Let's look at a simpler example first. Say I want a function that prints the value of i in a for loop, and I want it to run asynchronously:
for (i = 0; i < 5; i++) {
setTimeout(function() { console.log(i); }, 1);
}
Now this will actually only print out 5, 5 times. This is because the asynchronous function setTimeout returns after the loop has ended, and the value of i at the time that it executes is 5. If we want it to print out 0 - 4, then we need to do the following:
for (i = 0; i < 5; i++) {
(function(i) {
setTimeout(function() { console.log(i); }, 1);
})(i);
}
Notice how the anonymous function takes in i as a parameter. This creates a closure that "saves" the value of i at the time it was called.
Now back to your problem:
covariance = covariance + u * v;
This occurs before your console.log(covariance) statement, meaning that your print statement actually is occurring after the calculation. That means that covariance really is equal to 0 at this point.
Your covariance variable is initialized to 0. So are u and v. 0 + 0 * 0 = 0. Okay, great: so if u and v aren't being set properly, at least the math checks out.
Let's go back to your loop:
while (rating.length>0){
currentMovie = rating.pop();
var u=currentMovie.value-avarageRatingU;
var v=0;
Rating.find({movieid:currentMovie.movieid, user:a}, function(err,ratings) {
if (err) throw err;
if(ratings.length>0){
v=ratings.pop().value-avarageRatingV;
standardV=standardV+Math.pow(v,2);
covariance =covariance+u*v;
...
Here, we can see that your asynchronous Rating.find callback is pulling the last known value of u, i.e. its value at the end of the while loop. There isn't really any reason for v to be defined outside of the Rating.find callback.
If you want the value of u for each loop, try wrapping it in an anonymous self-executing function to "save" the value, like so:
Rating.find({user: b}, function(err,rating) {
var covariance = 0;
var standardU = 0;
var standardV = 0;
var u = 0;
while (rating.length > 0){
console.log("the avarage rating u is:" + avarageRatingU)
console.log("the avarage rating v is:" + avarageRatingV)
currentMovie = rating.pop();
u = currentMovie.value - avarageRatingU;
standardU = standardU + Math.pow(u, 2);
(function(u) {
Rating.find({ movieid: currentMovie.movieid, user: a }, function(err, ratings) {
if (err) throw err;
if (ratings.length > 0) {
var v = ratings.pop().value - avarageRatingV;
standardV = standardV + Math.pow(v,2);
covariance = covariance + u * v;
console.log(covariance);
}
});
})(u);
}
console.log(covariance)
callback(null,covariance);
//sim = covariance/(Math.sqrt(standardU)*Math.sqrt(standardV));
});
I've also moved the declaration of u outside of your loop (declaring them inside is bad practice, since you're reinstantiating the variable each time). I moved the declaration of v inside the Rating.find callback, as it's not even being used outside of there.
How about this:
Rating.find({user: b}, function(err,rating) {
var covariance=0;
var standardU=0;
var standardV=0;
var c = rating.length; // loop counter
var r = 0; // received counter
function aux_callback (covariance) {
r++;
if (r==c)
callback(null,covariance);
}
while (rating.length>0){
console.log("the avarage rating u is:" + avarageRatingU)
console.log("the avarage rating v is:" + avarageRatingV)
currentMovie = rating.pop();
var u=currentMovie.value-avarageRatingU;
standardU = standardU + Math.pow(u,2);
var v=0;
Rating.find({movieid:currentMovie.movieid, user:a}, function(err,ratings) {
if (err) throw err;
if(ratings.length>0){
v=ratings.pop().value-avarageRatingV;
standardV=standardV+Math.pow(v,2);
covariance =covariance+u*v;
console.log(covariance);
aux_callback(covariance);
}
})
}
//sim = covariance/(Math.sqrt(standardU)*Math.sqrt(standardV));
})
You need to trigger the callback from the lambda function inside the loop to make sure it does not happen before you run that code. Given that you seem to need all the loop to be completed, I created two counters, c to keep track of how many times the lambda has to run and r to count how many times you have called aux_callback. This code should wait until you have calculated all the covariances before calling callback
Which orm are you using?, I think a better approach is to group your results by "movieid", and after that compute the covariance for each group without make a query for each "rating" result.
Rating.find({user: b}, function(err, rating){
var covariance=0;
var standardU=0;
var standardV=0;
var ratingsByMovieId = groupByMovieId(rating);
while(rating.length > 0){
console.log("the avarage rating u is:" + avarageRatingU)
console.log("the avarage rating v is:" + avarageRatingV)
currentMovie = rating.pop();
var u=currentMovie.value-avarageRatingU;
standardU = standardU + Math.pow(u,2);
var v=0;
ratingsByMovieId[currentMovie.movieid].forEach(function(ratings){
// this code isn't asynchronous :)
// because the code isn't asynchronous you don't need a callback.
});
}
});
function groupByMovieId(ratings) {
var groups = {};
ratings.forEach(function(rating){
var movieId = rating.movieid;
groups[movieId] = groups[movieId] || [];
groups[movieId].push(rating);
});
return groups;
}
I'm not tested this code jeje :)
Related
How does counting instances of a callback function work in node.js?
I was working on the 9th exercise of learnyounode (below the official solution).
As I understand it, the httpGet function is called three times, running through process.argv[2], [3] and [4]. But how could count ever === 3? Don't the individual functions just get to one? How does one call of httpGet know of the other ones?
var http = require('http')
var bl = require('bl')
var results = []
var count = 0
function printResults () {
for (var i = 0; i < 3; i++)
console.log(results[i])
}
function httpGet (index) {
http.get(process.argv[2 + index], function (response) {
response.pipe(bl(function (err, data) {
if (err)
return console.error(err)
results[index] = data.toString()
count++
if (count == 3)
printResults()
}))
})
}
for (var i = 0; i < 3; i++)
httpGet(i)
But how could count ever === 3?
count is defined outside of httpGet and thus its value is independent of the those function calls. count++ is the same as count = count + 1, i.e. every call to httpGet increases the value of count by 1. The third time the function is called, count's value will be 3.
We can easily replicate this:
var count = 0;
function httpGet() {
count++;
console.log('count: ', count);
if (count === 3) {
console.log('count is 3');
}
}
httpGet();
httpGet();
httpGet();
First, prefer not using var.
var is defined in the global scope so it’s value updated between calls
Read more about var here
I have two problems i cant figure out. When i call GetParams first to get used defined values from a text file, the line of code after it is called first, or is reported to the console before i get data back from the function. Any data gathered in that function is null and void. The variables clearly are being assigned data but after the function call it dissapears.
let udGasPrice = 0;
let udGasLimit = 0;
let udSlippage = 0;
I want to get data from a text file and assign it to variables that need to be global. able to be assigned in a function but used outside it. So above is what i was doing to declare them outside the function. because if i declare them inside, i lose scope. It doesnt seem right to declare with 0 and then reassign, but how else can i declare them gloabaly to be manipulated by another function?
next the code is called for the function to do the work
GetParams();
console.log('udGasPrice = " + udGasPrice );
The code after GetParams is reporting 0 but inside the function the values are right
The data is read and clearly assigned inside the function. its not pretty or clever but it works.
function GetParams()
{
const fs = require('fs')
fs.readFile('./Config.txt', 'utf8' , (err, data) => {
if (err) {
console.error(err)
return;
}
// read file contents into variable to be manipulated
var fcnts = data;
let icnt = 0;
for (var x = 0; x < fcnts.length; x++) {
var c = fcnts.charAt(x);
//find the comma
if (c == ',') {
// found the comma, count it so we know where we are.
icnt++;
if (icnt == 1 ) {
// the first param
udGasPrice = fcnts.slice(0, x);
console.log(`udGasPrice = ` + udGasPrice);
} else if (icnt == 2 ) {
// second param
udGaslimit = fcnts.slice(udGasPrice.length+1, x);
console.log(`udGaslimit = ` + udGaslimit);
} else {
udSlippage = fcnts.slice(udGaslimit.length + udGasPrice.length +2, x);
console.log(`udSlippage = ` + udSlippage );
}
}
}
})
}
Like i said i know the algorithm is poor, but it works.(Im very noob) but why are the variables not retaining value, and why is the code after GetParams() executed first? Thank you for your time.
The code is executed before the GetParams method finishes, because what it does is an asynchronous work. You can see that by the use of a callback function when the file is being read.
As a best practice, you should either provide a callback to GetParams and call it with the results from the file or use a more modern approach by adopting promises and (optionally) async/await syntax.
fs.readFile asynchronously reads the entire contents of a file. So your console.log('udGasPrice = " + udGasPrice ); won't wait for GetParams function.
Possible resolutions are:
Use callback or promise
let udGasPrice = 0;
let udGasLimit = 0;
let udSlippage = 0;
GetParams(() => {
console.log("udGasPrice = " + udGasPrice);
});
function GetParams(callback) {
const fs = require('fs')
fs.readFile('./Config.txt', 'utf8', (err, data) => {
if (err) {
console.error(err)
return;
}
// read file contents into variable to be manipulated
var fcnts = data;
let icnt = 0;
for (var x = 0; x < fcnts.length; x++) {
var c = fcnts.charAt(x);
//find the comma
if (c == ',') {
// found the comma, count it so we know where we are.
icnt++;
if (icnt == 1) {
// the first param
udGasPrice = fcnts.slice(0, x);
console.log(`udGasPrice = ` + udGasPrice);
} else if (icnt == 2) {
// second param
udGaslimit = fcnts.slice(udGasPrice.length + 1, x);
console.log(`udGaslimit = ` + udGaslimit);
} else {
udSlippage = fcnts.slice(udGaslimit.length + udGasPrice.length + 2, x);
console.log(`udSlippage = ` + udSlippage);
}
}
}
callback()
})
}
fs.readFileSync(path[, options]) - it perform same operation in sync - you still need to edit your code accordingly
Also, it's advisable that you don't edit global variables in the function and return updated variables from the function.
I am lost in the promised land and could really use some guidance. I have exhausted searching numerous SO questions (2-3 hours of reading solutions + docs) related to this seemingly common issue and feel I just am not getting it.
Overview
Below I have code that takes in an Object type (resources), grabs a few values from this Object and then calculates distance and duration from the GoogleMaps Distance Matrix. The results of the function googleRequest() are a promise containing two values (distance and duration).
I would like to get these two values back within the for loop, execute pushToRows(), and then return an array called final_rows.
Problem
final_rows shows UNDEFINED for the duration and distance keys within each row. I speculate this is occurring because I am attempting to access the values in dist_dur inappropriately. I would appreciate any help on resolving this issue. Thanks.
Code
final_rows = []
function getDistTime(resources){
for (var i = 0; i < resources.data.length; i++) {
var origin1 = $("#citystate").val();
var destinationA = resources.data[i]['DEMOBILIZATION CITY'] + ',' + resources.data[i]['DEMOBILIZATION STATE'];
var dist_time_data = googleRequest(origin1, destinationA).then((values) => {
return values
})
pushToRows(resources.data[i], dist_time_data)
}
// console.log(final_rows)
}
function pushToRows(resources, dist_dur){
resources["DISTANCE_MI"] = dist_dur[0];
resources["ACTUAL_DUR_HR"] = dist_dur[1];
resources["FINANCE_DUR_HR"] = (dist_dur[0] / 45.0).toFixed(2)
final_rows.push(resources)
}
So, what you would need to do is just store promises in an array in the for loop and then wait for these promises to resolve using Promise.all but this would parallelize your requests to google distance api.
function getDistTime(resources){
const promiseArr = [];
for (var i = 0; i < resources.data.length; i++) {
var origin1 = $("#citystate").val();
var destinationA = resources.data[i]['DEMOBILIZATION CITY'] + ',' + resources.data[i]['DEMOBILIZATION STATE'];
promiseArr.push(googleRequest(origin1, destinationA));
}
// Not sure how would you use the data pushed in rows but since you are not waiting for promises to be resolved, data would be updated later on
return Promise.all(promiseArr)
.then((resultsArr) => {
resultsArr.forEach((result, i) => pushToRows(resources.data[i], result));
})
}
function pushToRows(resources, dist_dur){
resources["DISTANCE_MI"] = dist_dur[0];
resources["ACTUAL_DUR_HR"] = dist_dur[1];
resources["FINANCE_DUR_HR"] = (dist_dur[0] / 45.0).toFixed(2)
final_rows.push(resources)
}
I would recommend to use async-await which are syntactic sugar to promises but make your code easy to understand and remove the complications that come with promise chaining.
If you move your pushToRows() inside where you return values, you will have access to that data.
googleRequest(origin1, destinationA).then((values) => {
pushToRows(resources.data[i], values);
});
Until that promise resolves, dist_time_data would be undefined
You could also convert to Promise.all() which takes an array of promises and resolves when all of the promises are complete:
function getDistTime(resources){
const promises = [];
for (var i = 0; i < resources.data.length; i++) {
var origin1 = $("#citystate").val();
var destinationA = resources.data[i]['DEMOBILIZATION CITY'] + ',' + resources.data[i]['DEMOBILIZATION STATE'];
promises.push(googleRequest(origin1, destinationA));
}
return Promise.all(promises).then((results) => {
return results.map((result, i) => {
return {
...resources.data[i],
DISTANCE_MI: result[0],
ACTUAL_DUR_HR: result[1],
FINANCE_DUR_HR: (result[0] / 45.0).toFixed(2)
};
});
});
}
getDistTime(resources).then(result => {
//result is now "final_rows"
});
I'm having a hard time trying to understand promises, I'm sure I need to use them for this but I don't know how and other answers don't help me at all.
I'd like to loop over an array, query all the results for each value of the array, then after calculating the average value for these results, add the average in an array. After every iterations, this array is sent as a response.
Here is my code which could help understand me here:
Parse.Cloud.define('getScorePeopleArray', function(request, response) {
var peopleArray = request.params.peoplearray;
var query = new Parse.Query("Scores");
var resultat;
var index, len;
var resultarray = [];
var people;
for (index = 0, len = peopleArray.length; index < len; ++index) {
people = peopleArray[index];
query.equalTo("People",people);
query.find({
success: function(results) {
var sum = 0;
for (var i = 0; i < results.length; ++i) {
sum += results[i].get("Score");
}
resultat = (sum / results.length)*5;
if(!resultat){
resultarray.push("null");
}else{
resultarray.push(resultat);
}
},
error: function() {
response.error("score lookup failed");
}
}).then();
}
response.success(resultarray);
});
Of course response.success is not called when every queries are done, but as soon as possible (since queries are asynchronous if I'm right).
I know I have to change it with promises, but I don't understand at all how this works.
Thanks a lot in advance !
var _ = require('underscore');
Parse.Cloud.define('getScorePeopleArray', function(request, response) {
var peopleArray = request.params.peoplearray; // what is this an array of?
var resultArray = [];
return Parse.Promise.as().then(function() { // this just gets the ball rolling
var promise = Parse.Promise.as(); // define a promise
_.each(peopleArray, function(people) { // use underscore, its better :)
promise = promise.then(function() { // each time this loops the promise gets reassigned to the function below
var query = new Parse.Query("Scores");
query.equalTo("People", people); // is this the right query syntax?
return query.find().then(function(results) { // the code will wait (run async) before looping again knowing that this query (all parse queries) returns a promise. If there wasn't something returning a promise, it wouldn't wait.
var sum = 0;
for (var i = 0; i < results.length; i++) {
sum += results[i].get("Score");
}
var resultat = (sum / results.length) * 5;
if (!resultat){
resultArray.push("null");
} else {
resultArray.push(resultat);
}
return Parse.Promise.as(); // the code will wait again for the above to complete because there is another promise returning here (this is just a default promise, but you could also run something like return object.save() which would also return a promise)
}, function (error) {
response.error("score lookup failed with error.code: " + error.code + " error.message: " + error.message);
});
}); // edit: missing these guys
});
return promise; // this will not be triggered until the whole loop above runs and all promises above are resolved
}).then(function() {
response.success(resultArray); // edit: changed to a capital A
}, function (error) {
response.error("script failed with error.code: " + error.code + " error.message: " + error.message);
});
});
I have this data response from an AJAX call:
{"18:00":{"twopersons":1,"fourpersons":0}}
Which gets stored into a variable by statsarray = data;
Now how can i loop through statsarray and output the twopersons value?
So I can alert:
18:00 - There's 2 x 2persons and 0 x 4persons
Here is the Ajax call:
var statsarray;
var currentloopeddate = test_date.toString('yyyy-MM-dd')
$.post("/home/sessions",
{ action: 'partner_calendar_checkseats', date: currentloopeddate },
function(data) { statsarray = data; }
);
Just do the following:
var twopersons = data["18:00"].twopersons;
var fourpersons = data["18:00"]["fourpersons"];
(Both variants are possible)
A variant would be:
var shorter = data["18:00"];
var twopersons = data.twopersons;
// ...
Something like:
var tst = {"18:00":{"twopersons":1,"fourpersons":0}};
for(k in tst) {
for(var z in tst[k]) {
console.log(k + ": Theres "+tst[k][z] + " X " + z);
}
}
You can try something like this:
(UPDATE: better example)
var statsarray = {"18:00":{"twopersons":1,"fourpersons":0}};
var hour, persons, line, array;
for (hour in statsarray) {
if (statsarray.hasOwnProperty(hour)) {
array = [];
for (persons in statsarray[hour]) {
if (statsarray[hour].hasOwnProperty(persons)) {
array.push(statsarray[hour][persons] + " x " + persons);
}
}
line = hour + " - There's " + array.join(' and ');
alert(line);
}
}
See: DEMO.
Unfortunately you have to test with .hasOwnProperty to make sure it will work with some libraries.
UPDATE: You have added the code from your AJAX call in your question and I noticed that you declare the statsarray variable outside the callback function, but assign some value to that variable inside the callback. Just keep in mind that you have to run your iteration code inside the function that is the AJAX callback, where you have: statsarray = data; - just after this line, to make sure that you actually have some values to iterate over.