2

I'm building a function in Node that queries my mongoDB database and returns some results into an object. I'm having trouble getting the object set up correctly though. Here's my code:

Function to call the MongoDB lookup

  $scope.listItems = $resource('http://10.1.1.21\\:3000/api/uniques/:query');

  var itemsToList = [
    'designer',
    'store',
    'category'
  ]

  $scope.uniqueLists = {};

  for(var i = 0; i<itemsToList.length; i++){
    $scope.uniqueLists[itemsToList[i]] = [];  
    $scope.listItems.get({query:itemsToList[i]}, function(data){ 
        console.log(itemsToList); // Returns ["designer", "store", "Category"] x3
        console.log(itemsToList[i]); // Returns undefined, undefined, undefined
        console.log('i = ' + i); // Returns 3, 3, 3 (which is really odd, ideas?)

         $scope.uniqueLists[i] = data.query;

    });
  }

Issues with this code are in the comments above. Bizarrely, i == 3 for all 3 loops, when it should equal 0, 1, then finish at 2.

My question is

Can anybody shed light on what's going on here? It may have something to do with Node's asynchronous behaviour, but I'm really at a loss here.

Additional Info

My ExpressJS call that $scope.listItems is accessing is here, as you can see express makes data available through res(ponse) and as far as I know, I can't pass in i or itemsToList here:

exports.uniqueEntries = function(req, res){
    var query = req.params.query;
    console.log(query);
    db.products.distinct(query, function(err, results){
        if (err) {
            console.log("Lookup Error: " + err);
        } else{
            res.json({
                query:results  
            });
        }       
    });
}

Perhaps the issue is that by the time the expressJS lookup is finished, the three for loops have finished and i == 2. I have no idea why the console would log it as i ==3 though, nor do I have any idea of how to block the loop to ensure the lookup finished before continuing on with the next for loop.

JVG
  • 20,198
  • 47
  • 132
  • 210
  • possible duplicate of [Asynchronous Process inside a javascript for loop](http://stackoverflow.com/questions/11488014/asynchronous-process-inside-a-javascript-for-loop) – phant0m Jul 14 '13 at 08:53

2 Answers2

5

$resource methods, including get(), are asynchronous. So, their callbacks will be called later. And, in this case, later is after the for loop has run to completion.

For any statements inside the callback, i will have already been incremented to 3, which is why it's no longer 0, 1, or 2 for:

console.log('i = ' + i);

You can keep each value of i as it's incremented by creating a closure for it around the callback function:

function createCallback(itemsToList, i) {
  return function (data) {
      console.log(itemsToList); // Returns ["designer", "store", "Category"] x3
      console.log(itemsToList[i]); // Returns undefined, undefined, undefined
      console.log('i = ' + i); // Returns 3, 3, 3 (which is really odd, ideas?)

       $scope.uniqueLists[i] = data.query;
  };
}

for(var i = 0; i<itemsToList.length; i++){
  $scope.uniqueLists[itemsToList[i]] = [];  
  $scope.listItems.get({query:itemsToList[i]}, createCallback(itemsToList, i));
}

Another option, assuming itemsToList is an Array, can be to use .forEach() in-place of the for loop as the iterator will be the closure:

itemsToList.forEach(function (item, i) {
  $scope.uniqueLists[item] = [];
  $scope.listItems.get({query:item}, function(data){ 
    console.log(itemsToList);
    console.log(itemsToList[i]);
    console.log('i = ' + i);

    $scope.uniqueLists[i] = data.query;
  });
});
Jonathan Lonowski
  • 121,453
  • 34
  • 200
  • 199
  • Fantastic, that's done it. Cheers, I'm new to `async` and this really helps a lot. – JVG Jul 14 '13 at 12:16
0

When you take a look at the jQuery documentation(http://api.jquery.com/jQuery.get/), you'll find that $.get is short for:

$.ajax({
    url: url,
    data: data,
    success: success,
    dataType: dataType
});

The async propery of an ajax call is set to true by default.. (http://api.jquery.com/jQuery.ajax/) So change the get request for an ajax call with the property async set to false, i think that is the problem here.

Mark Rijsmus
  • 627
  • 5
  • 16
  • In this case, `.get()` isn't referring to `jQuery.get()`. AngularJS `$resource`s [have their own `.get()`](http://docs.angularjs.org/api/ngResource.$resource#Returns). – Jonathan Lonowski Jul 14 '13 at 08:44
  • Ok, well i still think its a sync/async problem by the sound of it.. Try to find a workaround within AngularJS or go for the jQuery.ajax(). – Mark Rijsmus Jul 14 '13 at 08:48
  • I'm not using jQuery here at all. – JVG Jul 14 '13 at 12:15