How to handle Simultaneous javascript xmlhttprequests?

nikolas picture nikolas · Nov 19, 2012 · Viewed 12.8k times · Source

Possible Duplicate:
passing index from for loop to ajax callback function (javascript)

I've been a little confused with making xmlhttprequests, to different servers, in order to fetch some content.. Here is what I've written, but it seems that I'm mistaken at some point..

var URL = new Array();
URL[0] = "http://www.example1.com";
URL[1] = "http://www.example2.com";
URL[2] = "http://www.example3.com";
var nRequest = new Array();
for (var i=0; i<3; i++) {
    nRequest[i] = new XMLHttpRequest();
    nRequest[i].open("GET", URL[i], true);
    nRequest[i].onreadystatechange = function (oEvent) {
        if (nRequest[i].readyState === 4) {
            if (nRequest[i].status === 200) {
                console.log(nRequest[i].responseText);
                alert(nRequest[i].responseText);
            } else {
                console.log("Error", nRequest[i].statusText);
            }
        }
    };
    nRequest[i].send(null);
}

with this code on I.E.10 I get access denied on console..

If I remove array and use simple request, it operates as expected..

wRequest = new XMLHttpRequest();
wRequest.open("GET", "http://www.example1.com", true);
wRequest.onreadystatechange = function (oEvent) {
    if (wRequest.readyState === 4) {
        if (wRequest.status === 200) {
            console.log(wRequest.responseText);
            alert(wRequest.responseText);
        } else {
            console.log("Error", wRequest.statusText);
        }
    }
};
wRequest.send(null);

But how am I supposed to trigger multiple 2-3 requests, and still not have problem with data handling..??

Answer

nnnnnn picture nnnnnn · Nov 19, 2012

The problem (ignoring the cross-domain issue that slebetman covered) is that when your ready state change callback is fired it is using the i variable from the containing scope which will be 3 after the loop completes. One way to fix that is as follows:

for (var i=0; i<3; i++){
   (function(i) {
      nRequest[i] = new XMLHttpRequest();
      nRequest[i].open("GET", URL[i], true);
      nRequest[i].onreadystatechange = function (oEvent) {
         if (nRequest[i].readyState === 4) {
            if (nRequest[i].status === 200) {
              console.log(nRequest[i].responseText);
              alert(nRequest[i].responseText);
            } else {
              console.log("Error", nRequest[i].statusText);
            }
         }
      };
      nRequest[i].send(null);
   })(i);
}

This introduces an immediately invoked function expression for each loop iteration such that the code inside the function has its own i - the magic of JS closures means that when the onreadystatechange function is called it will access the parameter i of the anonymous function (even though that function has completed), not the i of the outer scope, so the right nRequest element will be processed each time.

Also you had a typo on the .open() line where you said wURL[i] but should've had URL[i].

Depending on what you plan to do with the response text I'm not sure that you need an array of requests at all: you could encapsulate the Ajax code into a function that takes a URL and a callback function as parameters, and then call that function in the loop...