3

The Problem

When working with AJAX to query a remote API, by the asynchronous nature of the request it comes back whenever it completes. The problem is when I have to make iterative calls to the same API with different criteria, I don't know which response is coming back.

The question: Is it possible to pass a variable from

Sample code: (simplified)

n=5;
for(i=0; i < n; i++) {
  $.ajax({
    url: someURL,
    method: post,
    // I don't want to have to use async: false, that's bad
    // async: false,
    data: JSON.stringify(someData),
    beforeSend: function(){
      console.log("Starting request #"+i)
    },
    error: function(err, code, text) {
      alert("Something went wrong: \n\n" + code + ": " + text);
    },
    success: function(response) {
      console.log("Response for request #"+i)
      console.log(response)
    }
  })
}

The problem comes up in that final success function: What I should see is:

Starting request #0
Starting request #1
Starting request #2
Starting request #3
Starting request #4
Response for request #2
[object]
Response for request #1
[object]
Response for request #4
[object]
Response for request #0
[object]
Response for request #3
[object]

What I actually see is:

Starting request #0
Starting request #1
Starting request #2
Starting request #3
Starting request #4
Response for request #4
[object]
Response for request #4
[object]
Response for request #4
[object]
Response for request #4
[object]
Response for request #4
[object]

This is important not because I care about my logs being right, but because the actual version needs to be able to reconcile the responses with what was sent. I don't want to go synchronous on this, because it'll be slow, annoying, and possibly time out.

Calciphus
  • 146
  • 1
  • 13
  • Search for "javascript last value in loop" and you'll find duplicates like http://stackoverflow.com/questions/2520587/variable-in-javascript-callback-functions-always-gets-last-value-in-loop , http://stackoverflow.com/questions/6425062/passing-functions-to-settimeout-in-a-loop-always-the-last-value, and http://stackoverflow.com/questions/9439700/javascript-function-is-using-the-last-known-parameters-in-loop?lq=1 (and cute, there is even a comment from my previous incarnation as user166390!) – user2864740 Apr 02 '15 at 01:04
  • possible duplicate of [JavaScript closure inside loops – simple practical example](http://stackoverflow.com/questions/750486/javascript-closure-inside-loops-simple-practical-example) (from a related link in a related question) – user2864740 Apr 02 '15 at 01:07

2 Answers2

5

As mentioned in the comments, this is a special case of a more common mistake with closures in loops. Since the value of i will have changed by the time success runs, you'll want to store it (by calling a helper function outside the loop which makes the ajax request) or bind i to the success function. Example:

for (var i = 0; i < 5; i++) {
    $.ajax({
        url: '/url',
        success: function (i, response) {
            console.log("Response for request #"+i);
            console.log(response);
        }.bind(window, i)
    });
}
Community
  • 1
  • 1
Don McCurdy
  • 10,975
  • 2
  • 37
  • 75
  • 1
    This was immensely helpful, thank you! That absolutely resolved my issue. Digging into closures a bit more was also really helpful. – Calciphus Apr 06 '15 at 07:43
  • `.bind(window, i)` kinda visually resembles `window.i = i;`. Is that what it's doing? – kiradotee Sep 22 '16 at 09:34
  • @kiradotee that's [Function.prototype.bind(context, arg1, arg2, ...)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind) — `context`/`window` will become `this` when the function runs, and `i` gets preloaded as the first argument to the function. So `window` is just a placeholder, could also have used `null`. – Don McCurdy Sep 23 '16 at 03:10
1

Use a function to make the AJAX request, and pass i to that function:

var n = 5;
for(i = 0; i < n; i++) {
  makeRequest(i);
}

function makeRequest(i) {
  $.ajax({
    url: url,
    method: 'POST',
    data: JSON.stringify(someData),
    beforeSend: function(){
      console.log("Starting request #"+i)
    },
    error: function(err, code, text) {
      console.log("Response for request #"+i)
      ...
    },
    success: function(response) {
      console.log("Response for request #"+i)
      ...
    }
  });
}
Michael Bates
  • 1,884
  • 2
  • 29
  • 40