Aug 24 2006 02:33Multiple XMLHTTPRequest Objects Redux
My old article on this subject provided code that worked, but wasn't terribly efficient and did a few unnecessary things. I can't let that just sit there, so here's a better way!
For those new to AJAX/Web 2.0/[Buzzword here], one of the main features is the use of an XMLHTTPRequest object to fetch data in the background without actually changing pages. Most examples show you how to do this, but they don't address what happens when you make multiple simultaneous requests. If you're using a global request object, subsequent requests will overwrite previous ones and you'll never receive a response. That's probably not what you want to happen.
My first solution used a global array of objects, adding them when created and then deleting them once used. This worked, but it required some kludges and it didn't re-use xmlhttp objects. What's the better solution?
var xmlreqs = new Array(); function CXMLReq(freed) { this.freed = freed; this.xmlhttp = false; if (window.XMLHttpRequest) { this.xmlhttp = new XMLHttpRequest(); } else if (window.ActiveXObject) { this.xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); } } function xmlreqGET(url) { var pos = -1; for (var i=0; i<xmlreqs.length; i++) { if (xmlreqs[i].freed == 1) { pos = i; break; } } if (pos == -1) { pos = xmlreqs.length; xmlreqs[pos] = new CXMLReq(1); } if (xmlreqs[pos].xmlhttp) { xmlreqs[pos].freed = 0; xmlreqs[pos].xmlhttp.open("GET",url,true); xmlreqs[pos].xmlhttp.onreadystatechange = function() { if (typeof(xmlhttpChange) != 'undefined') { xmlhttpChange(pos); } } if (window.XMLHttpRequest) { xmlreqs[pos].xmlhttp.send(null); } else if (window.ActiveXObject) { xmlreqs[pos].xmlhttp.send(); } } } function xmlreqPOST(url,data) { var pos = -1; for (var i=0; i<xmlreqs.length; i++) { if (xmlreqs[i].freed == 1) { pos = i; break; } } if (pos == -1) { pos = xmlreqs.length; xmlreqs[pos] = new CXMLReq(1); } if (xmlreqs[pos].xmlhttp) { xmlreqs[pos].freed = 0; xmlreqs[pos].xmlhttp.open("POST",url,true); xmlreqs[pos].xmlhttp.onreadystatechange = function() { if (typeof(xmlhttpChange) != 'undefined') { xmlhttpChange(pos); } } xmlreqs[pos].xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xmlreqs[pos].xmlhttp.send(data); } } function xmlhttpChange(pos) { if (typeof(xmlreqs[pos]) != 'undefined' && xmlreqs[pos].freed == 0 && xmlreqs[pos].xmlhttp.readyState == 4) { if (xmlreqs[pos].xmlhttp.status == 200 || xmlreqs[pos].xmlhttp.status == 304) { handle_response(xmlreqs[pos].xmlhttp.responseXML); } else { handle_error(); } xmlreqs[pos].freed = 1; } }
What does all of that do? Well, it's still the same basic idea of the first method - keep all of our xmlhttp objects in a global array. This time I've expanded on the use of a class to hold the object - it now holds the object and keeps track of whether or not that object is free to re-use. The xmlhttp object is also created when the class is instantiated, which saves us quite a bit of code inside the xmlreqGET/POST functions.
When either xmlreq function is called, it searches the array for a free object and sets pos equal to that object's position (or to the end of the array if no free objects are available). This part saves on resources, as we're not creating a new request object every time we need to use it. Unless you're making a huge amount of requests, you'll probably only use one or two objects most of the time. Note that IE does not enjoy it if you set the onreadystatechange before calling open, so make sure you keep them in the proper order.
The onreadystatechange function is now declared inline, so that we can pass an argument to xmlhttpChange. Rather than loop through the array every time we get a result, just tell it which item we want to use in the first place. Since we never remove items from the array, passing pos won't cause problems.
The xmlhttpChange function is much more compact in this version, and there's no need to construct a new xml document - you can just pass the responseXML straight through. After handling the response or dealing with an error, just set freed to 1 in order to re-use the xmlhttp object.
The whole thing is now less complicated and it re-uses xmlhttp objects. And there was much rejoicing.
billyThaKid Aug 30 2006 16:13
This is a great concept. Do you have an example of this using "GET" and returing that data to an innerhtml element?
Matt Sep 5 2006 21:26
Check some of the links in the old article for the basics of sending and receiving requests - if you just want to put the entire response into an element, you can use responseText instead of responseXML.
For example: document.getElementById('para').innerHTML = xmlreqs[pos].xmlhttp.responseText;
Using that instead of handle_response() in the code above would replace the contents of the element with id 'para' with the response from the xml request.
watch Sep 8 2006 17:43
Smaller, clever:
http://kb.mozillazine.org/XMLHttpRequest#Multiple_asynchronous_calls
Matt Sep 8 2006 21:35
Clever, but appears to only work in Mozilla.
david Nov 14 2006 20:36
Very very smart. It would make my job much easier. what are the terms of use of this function by the way?
Matt Nov 16 2006 03:51
It's free to use, though I would appreciate credit if it's posted anywhere else.
David Nov 17 2006 19:27
Thanks alot. i am not posting it anywhere i was just gonna use it on a project i am working on. thanks again. you RoCKK!
Daniel Rodrigues Jan 4 2007 12:47
If you don't care about the order of the requests, you may as well use pop() and push() in the arrays... it will greatly simplify your life, i think, but I haven't done a great in depth on the code :S