b11 b11 - 3 months ago 18
Ajax Question

How to keep a counter inside recursive AJAX call?

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?

Answer

Your main issue is a fairly common one with callbacks which is that you're treating ajax like a synchronous call, when it's 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.

The easiest? way for you to achieve what you want is to define count in the outer scope of load() and increment it on each success callback from the ajax() within load(). Whenever you stop your recursion (i.e. reach your base case of maximum rows and don't load anything anymore) you can print count:

var count = 0;

function load(row) {
   $.ajax({
      // url and other stuff
      // ...

      success: function(response) {

        // ... loop that increments the count

        // logic that checks row+1 is not out of bounds
        // ...
        if ( /* your base case is reached */ ) {
          console.log(count);
        } else {
          load(row + 1);
        }
      }
    });
  }
}

A cleaner and more reasonable approach would be to use Promises. You can have the load() method return a Promise that will be resolved on success. Additionally, since you're not returning anything from load(), you can pass count through the Promise chain instead of defining it globally:

function loadWithPromise(row, count) {
  return new Promise(function(resolve) {
    $.ajax({
      // url and other stuff
      // ...

      success: function(response) {

        // ... loop that increments the count

        // if we reached max rows, resolve this promise (and thus the whole chain)
        if ( row === MAX_ROWS ) {
          resolve(count);
        } 
        // otherwise load the next promise
        else {
          return 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 don't even need recursion. The drawback though is that you would need to have your count defined globally since promises would now be unrelated:

var count = 0;

function loadWithPromise(row) {
  return new Promise(function(resolve) {
    $.ajax({
      // url and other stuff
      // ...

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