0

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

});
Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
sanch
  • 696
  • 1
  • 7
  • 21
  • Trying to use a `for` loop index in an async callback. There are hundreds of dups of this. I will see if I can find one. – jfriend00 Mar 25 '15 at 17:01
  • I am having a little bit of trouble understanding this after looking at that post. Here I have two async functions, so the `usersFriends` data needs to go through two async functions. I have little understanding of closures, so I am thoroughly confused . I updated the code to use the for loop solution from the referenced post. – sanch Mar 25 '15 at 17:35
  • I reopened it. That did solve your first issue with the `for` loop index. But, you appear to have additional problems in that you need to know when all the inner `.findOne()` operations are done so you can then know when to use the `userFriends` result. There are many dups of that part too. – jfriend00 Mar 25 '15 at 17:39
  • Great thanks! That is the problem yes, once the async functions are done, i need the usersFriends data to be passed back outside to the `usersFriends` array so it can be sent with the chunk. – sanch Mar 25 '15 at 17:42
  • And FYI, this isn't a scope issue. It's a timing issue. Asynchronous operations finish sometime in the future. You are doing a `console.log(userFriends)` before any of the async responses have finished. – jfriend00 Mar 25 '15 at 17:42
  • I realized that, I changed the question name – sanch Mar 25 '15 at 17:43
  • See the techniques used in this answer for maintaining a counter of when all responses are done (also can use promises): http://stackoverflow.com/questions/29051467/why-is-this-fs-readfile-loop-not-pushing-its-results-to-my-array/29051632#29051632. Other examples http://stackoverflow.com/questions/24989375/ajax-asynch-callback-working-correctly-but-how-do-i-wait-for-the-returned-values/24989456#24989456 and http://stackoverflow.com/questions/27119280/how-to-collect-the-value-to-an-array-in-nodejs-loop/27119344#27119344 – jfriend00 Mar 25 '15 at 17:49

1 Answers1

0

Combining all the various things we've discussed in comments for this list of changes:

  1. Pass the cntr in an IIFE so it is uniquely captured for each separate .findOne() request.
  2. In each response, check if this is the last one done so we know that all results have arrived.
  3. 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);

                    }
                }
            });
        }
    });
});     
jfriend00
  • 683,504
  • 96
  • 985
  • 979