16

I need to get a range of pages using AJAX and put them into an array, where their given place in the array is equal to the i of a for loop (it's a caching-like function for blog pages, and the range of the for loop is entirely variable). I'm doing something akin to the following:

var bongo = new Array();

for (i = 0; i < 10; i++) {

    jQuery.ajax({ type: "GET", url: 'http://localhost', data: queryString, success: function(request) { bongo[i] = request } })

}

The problem is, that unless I add async: false to the .ajax options (which would make it... SJAX?), which causes the requests to basically pause the browser, going against what I'm trying to do, the i in the success callback will always end up being 11, whereas I of course want it to pour the returned data into each slot of the array from 0 to 10.

I've tried replacing the line with this:

bongo[i] = jQuery.ajax({ type: "GET", url: 'http://localhost', data: queryString }).responseText

But that made no difference.

Cerbrus
  • 70,800
  • 18
  • 132
  • 147
Michael Heilemann
  • 2,627
  • 4
  • 23
  • 28

2 Answers2

39

You need a closure:

var bongo = [];
for (i = 0; i < 10; i++)
{

  (function(i)
    {
      jQuery.ajax(
        {
          type: "GET",
          url: "http://localhost",
          data: queryString,
          success: function(request) { bongo[i] = request } 
        });  
    })(i);
}

Loops are the #1 place where inline functions stump people. The bongo[i] = result isn't called until later. The value of i at that time is different (most likely 11). If you want to "trap" or "capture" the current value of i, you need to create a new scope. The only way to do that in javascript is with another function.

Plynx
  • 11,341
  • 3
  • 32
  • 33
  • It works like a charm. But I didn't get what the final (i) does, could someone explain that? – Kar.ma Aug 05 '16 at 10:48
  • the value of outer i gets passed into the wrapping self-executing anonymous function; this unique value's location gets captured by the async callback. In this way, each async gets its own value, determined at the moment the self-executing function is invoked. That's the point of a closure!! ;) – Plastic Aug 26 '16 at 08:46
4

Try:

var bongo = [];
for (i=0; i<10; i++) {
  $.get("http://localhost", function(result) {
    bongo.push(result);
  }
}

This way each result will simply get pushed onto the array, solving the need for the array indexes to be correct. Order however is not guaranteed. If that is a requirement you'll need another approach.

There are multiple ways to solve this problem. Here is one: create objects for your callback to save state. Here is an example:

function Callback(array, index, result) {
  this.array = array;
  this.index = index;
  this.result = result;
  var obj = this;
  this.func = function() {
    obj.array[obj.index] = obj.result;
  };
}

$(function() {
  var arr = [];
  for (var i=0; i<4; i++) {
    var obj = new Callback(arr, i, "result" + i);
    setTimeout(obj.func, (5-i) * 100);
  }
  setTimeout(function() {
    console.log(arr);
  }, 500);
});

So in your case:

function Callback(array, index) {
  this.array = array;
  this.index = index;
  var obj = this;
  this.callback = function(result) {
    obj.array[obj.index] = result;
  };
}

var bongo = [];
for (i=0; i<10; i++) {
  var ob = new Callback(bongo, i);
  $.get("http://localhost", ob.callback);
}

Basically the above saves all the data to an object and thus each callback has access to the right information.

Also, bear in mind that most browsers limit the number of concurrent AJAX requests, typically to 2 per host.

cletus
  • 616,129
  • 168
  • 910
  • 942