1

I have rows of items. Each row has a different number of items. I want a counter that keeps track of the total number of items loaded. I have a function that recursively loops through all the rows via AJAX calls, like this (I have commented out the stuff that is not relevant to the problem)

$(document).ready(function () {
    var num_loaded = 0;
    var row = 0;
    load(row, num_loaded);
}

function load(row, num_loaded) {
    var count = num_loaded;
    $.ajax({

        // url and other stuff
        // ...

        success: function (response) {
            for (i = 0; i < response.items.length; i++) {

                // load item[i] 
                // ...

                count++;
            }

            // logic that checks row+1 is not out of bounds
            // ...

            load(row+1, count);
        } 
    })

    console.log(count);
}

As you can see, upon success, each load function calls itself and pass in a parameter that keeps track of the count. This does not work and I keep getting NaN or undefined in the console. How do I accomplish this and see only a final count of how many items are loaded?

1 Answers1

0

Your main issue is a fairly common one with callbacks which is that you're treating ajax like a synchronous call, when it's actually asynchronous. In other words, you are trying to print count right after the call to ajax, but count won't be updated by that time.

var count = num_loaded;
$.ajax({
  // ... this updates count some time later
});
// this happens right after we call $.ajax but before the success callback
// so count still has the same value as before we called $.ajax
console.log(count);

Here's a related question you can take a look at

Additionally, you're defining your counter inside the load() method, which means it will be recreated on the call stack for each function call.

If you pass count recursively through your async callbacks, it will have the correct value whenever your recursion stops (i.e. reaches the base case of maximum rows) so you must print it inside the callback:

$(document).ready(function () {
  load(0, 0);
}

function load(row, count) {
   $.ajax({
      // ...    
      success: function(response) {

        // ... loop that increments the count

        if ( /* your base case is reached */ ) {
          console.log(count);
        } else {
          load(row + 1, count);
        }
      }
    });
  }
}

A cleaner and more reasonable approach would be to use Promises. You can have the load() method return a Promise that will on success either:

  • resolve to the promise that loads the next row, or
  • print count if the base case is reached.

Since Promises are chainable, we can also easily pass count recursively:

function loadWithPromise(row, count) {
  return new Promise(function(resolve) {
    $.ajax({
      // ...    
      success: function(response) {

        // ... loop that increments the count

        // if we reached the base case, resolve this promise (and thus the whole chain)
        if ( /* your base case is reached */ ) {
          resolve(count);
        } 
        // otherwise load the next promise
        else {
          resolve(loadWithPromise(row + 1, count));
        }
      }
    });
  });
}

// recursively load all rows and then print count once we're done
loadWithPromise(0, 0).then(function(count) {
  console.log(count);
});

If the rows don't need to be loaded sequentially, you can use Promise.all to wait for all calls to load() to finish before printing the count. This way you can keep all your promises in an array and let them happen independently, plus you wouldn't need recursion. The drawback though is that you would need to have count defined globally since these promises would now be unrelated:

var count = 0;

function loadWithPromise(row) {
  return new Promise(function(resolve) {
    $.ajax({
      // ...    
      success: function(response) {

        // ... loop that increments the count

        resolve();
      }
    });
  });
}

var promises = [];
for (var row = 0; row < MAX_ROWS; row++) {
  promises.push(loadWithPromise(row));
}

// once all loading operations have resolved, print the count
Promise.all(promises).then(function() {
  console.log(count);
});
Graham
  • 7,431
  • 18
  • 59
  • 84
nem035
  • 34,790
  • 6
  • 87
  • 99
  • When you say initiate counter in the outer scope of load, does that mean outside of all functions or can it be inside document.ready on page load? –  Sep 02 '16 at 03:41
  • also that means I am setting counter as a global variable, won't that cause memory leaking problems? –  Sep 02 '16 at 03:43
  • If you don't want something to be global, just wrap it in an [IIFE](http://stackoverflow.com/documentation/javascript/4655/modularization-techniques/16340/immediately-invoked-function-expressions-iife#t=201609020345127395816). As far as memory leaks, this isn't really an issue with primitive data such as numbers. If you were for example storing DOM node references globally which would then be accessible to some functions via their closures, there's a chance those wouldn't be garbage collected properly, but you don't really have such a concern. – nem035 Sep 02 '16 at 03:45
  • As far as your first question, you should *usually* wrap all of your code in the `document.ready`. – nem035 Sep 02 '16 at 03:53
  • I edited to show how to do the first approach without global vars – nem035 Sep 02 '16 at 04:02