0

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 :)

1
  • 1
    Did you even search? "I thought about using async.series, however, I can not have the callback function inside the while loop." What does that mean? If you use one if the async functions instead, you won't have a while loop anymore. Commented Oct 28, 2015 at 20:02

8 Answers 8

2

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);
    }
  })
}
Sign up to request clarification or add additional context in comments.

7 Comments

I actually thought this would work, but it didn't print out anything.. But thx for the tip!
You're welcome.It could be that ratings isn't bigger than 0 right? Try to put the if(!isBiggerThanZero){etc..} after your last if statement: if(ratings.length>0){etc... } instead of inside.Maybe this helps
yeah you're right. FIxed that now, however. In my last function in my async.series, : function(err,result){ console.log(avarageRatingU); console.log(avarageRatingV); console.log(covariance); }); It actually prints out the right values for each of them. However, after printing out, I get a "Error: Callback was already called."
this is so weird. So it looks like the functions inside if(isBiggerThanZero == false) statements stores the answers (covariance) untill isbiggerthanzero is false, and then prints out all the answers, hence, the callback happens multiple times.
Did you see that I edited my answer? like this I think it should basically be the same than before just written in a different way. I'm sorry if I can't help you more, I'm still learning Javascript. And yes, this sounds weird.
|
1

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

1 Comment

I suggested async.eachSeries, but I like this answer better. Great use of functional programming, since your results array could be easily reduced to the covariance, and no state/global variables needed.
0

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

1 Comment

That won't work. At the moment you call Q.allSettled, promises is an empty array.
0

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!

Comments

0

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

Comments

0

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.

2 Comments

Hi! ty for input. I took advice about taking u outside, and v inside:) However, the "console.log(covariance)" inside the loop is correct, but its not the one I want however, it is the second console.log(covariance) that is printing out 0, that I want fixed:)
I see what you're saying. Within the loop, set some variable equal to your Rating.find call, then use a when statement to execute your callback when the last Rating.find call happens. Look at an example pure JS when function here.
0

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

3 Comments

Thx, nice idea! However, wouldnt c always be bigger than r? meaning the callback will never be called
You are right, there was a little problem with the 'r' index, I fixed it and also initialized 'c' to the length of the loop so there's no risk about lambda functions being executed as fast as the loop :)
Theres another problem, ratings.length might not be greater than 0, meaning aux_callback, wont be called. So I took it outside my last if statement. however, i still get 0 as covariance
0

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 :)

1 Comment

orm = object relational mapper, something like mongoose or sequelize. I ask, because maybe your orm has some way to group your result by "movieid".

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.