Aug 9 2006 02:08Handling Multiple XMLHTTPRequest Objects
This is the old article - my newer, better method can be found here.
If you're using XMLHTTPRequest objects in your AJAX application and you send multiple requests simultaneously, you might notice some problems. There are plenty of examples out there to get you started, but none of them cover this issue (or at least they didn't a year ago).
When using a single global xmlhttp object and sending multiple requests, a second request sent before the first has a chance to return will overwrite the first request and you'll never hear from it again.
What can you do to avoid this? Use a global array of xmlhttp objects, like so:
var xmlreqs = new Array(); function CXMLReq(type, xmlhttp) { this.type = type; this.xmlhttp = xmlhttp; } function xmlreqGET(url) { var xmlhttp=false; if (window.XMLHttpRequest) { // Mozilla, etc. xmlhttp=new XMLHttpRequest(); xmlhttp.onreadystatechange = xmlhttpChange; xmlhttp.open("GET",url,true); xmlhttp.send(null); } else if (window.ActiveXObject) { // IE xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); if (xmlhttp) { xmlhttp.onreadystatechange = xmlhttpChange; xmlhttp.open("GET",url,true); xmlhttp.send(); } } var xmlreq = new CXMLReq('', xmlhttp); xmlreqs.push(xmlreq); } function xmlreqPOST(url,data) { var xmlhttp=false; if (window.XMLHttpRequest) { // Mozilla etc. xmlhttp=new XMLHttpRequest(); xmlhttp.onreadystatechange=xmlhttpChange; xmlhttp.open("POST",url,true); xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xmlhttp.send(data); } else if (window.ActiveXObject) { // IE xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); if (xmlhttp) { xmlhttp.onreadystatechange=xmlhttpChange; xmlhttp.open("POST",url,true); xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xmlhttp.send(data); } } var xmlreq = new CXMLReq('', xmlhttp); xmlreqs.push(xmlreq); } function xmlhttpChange() { if (typeof(window['xmlreqs']) == "undefined") return; var xmldoc = null; for (var i=0; i < xmlreqs.length; i++) { if (xmlreqs[i].xmlhttp.readyState == 4) { if (xmlreqs[i].xmlhttp.status == 200 || xmlreqs[i].xmlhttp.status == 304) { if (document.implementation && document.implementation.createDocument) { xmldoc = document.implementation.createDocument("", "", null); } else if (window.ActiveXObject) { xmldoc = new ActiveXObject("Microsoft.XMLDOM"); } xmldoc = xmlreqs[i].xmlhttp.responseXML; xmlreqs.splice(i,1); i--; handle_response(xmldoc); } else { // error xmlreqs.splice(i,1); i--; } } } }
First it sets up the global array, and I also define a class to hold the xmlhttp objects. You can skip that step and just add the xmlhttp objects directly into the array, but using a class gives you some room to associate data with each request if you want.
The xmlreqGET and xmlreqPOST functions create a new xmlhttp object and add it to the array. The xmlhttpChange function first checks to see if the xmlreqs array exists (needed sometimes when making a request from an onunload function) and then loops through the array looking for objects that are ready.
If an object is ready and has some data, a new XML document is created and the responseXML from the request is copied to it. Once that is copied, the object can be removed from the array and the response can be dealt with. Why create a new XML object and handle the response after removing the object? Errors will occur if you make a new request while handling the response from another - this is because the object you're handling the response of is marked as ready, and when your newly created object triggers the xmlhttpChange function the response from the original request (which is still ready) will be handled again. If you want to generate an infinite recursion error in Firefox, try that out :)
Thanks to Chris Denham for pointing out a few possible bugs, one of which is fixed by the addition of i--; after splicing an object. The other possible bug (also noted by M.J.A.W.) is that request objects are added to the array after they're sent, so if somehow you receive a response before the object can be added to the array, it won't be processed. I've never seen this happen, but if you're concerned about it you can add the objects to the array right after they're created and then call the rest of the methods directly from the array, e.g. xmlreqs[xmlreqs.length-1].xmlhttp.open("GET",url,true); etc.
The downside to this method is that it requires creating a new XML document (which will take some time) and that it doesn't re-use the xmlhttprequest objects. I'll be working on a newer version that solves both of those problems, but in the meantime this code has proven very solid for me.
Andrei Aug 9 2006 19:57
I use another solution for handling multiple requests. I simply pass the http_request object to the onreadystatechange function:
http://dominounlimited.blogspot.com/2006/08/making-simultaneous-ajax-requests.html
michael Aug 15 2006 05:43
Thank you for the array idea.
mdm-adph Sep 1 2006 10:58
Works for me! Great stuff, and a wonderful solution to a problem that hardly anyone seems to address.
Hakon Sep 12 2006 14:27
this works great - thanks! The idea of the class to hold the xmlhttp objects is actually magic - allows to pass so much more information along when creating the request.
Matt Oct 19 2006 20:39
Everyone who is finding this article, be sure to check out the newer one as well (linked at the top of this page).