分享

Drakware - Handling Multiple XMLHTTPRequest Objects

 karoc 2006-04-21
Handling Multiple XMLHTTPRequest Objects
At the center of any AJAX application is the XMLHTTPRequest object that allows for asynchronous data transfer in the background without reloading the entire web page. It allows for very responsive web sites and a dramatically enhanced user experience. Most examples on the web are adequate, but only work for one request at a time. In applications that send many requests simultaneously or in situations where network latency is unsteady these examples fall short.
Here‘s a typical AJAX example - there are many on the web, so I won‘t describe how to make use of these functions:
var xmlhttp=false; function xmlreqGET(url) { 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(); } } } function xmlreqPOST(url,data) { 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); } } } function xmlhttpChange() { if (xmlhttp.readyState == 4) { if (xmlhttp.status == 200 || xmlhttp.status == 304) { // 200 OK handle_response(); } } else { // error } }
For some implementations this works fine. For those where simultaneous requests are made at very close intervals however, this will cause headaches. For example, say you have a page that updates stock symbols at a certain interval. Every 30 seconds it sends a new request in the background and receives updated stock data. The page also uses XMLHTTPRequests to load content in the center of the page, in response to a user clicking buttons. What happens if the user clicks a button at or around the same time that the stock request is sent?
If the stock request has been sent but no reply has been received, and since we‘re only using one global variable to hold the request object, the new request overwrites it. The second request will proceed as usual, but the reply to the first request will be lost. Losing a request is obviously a problem, and this sort of situation could also occur when network latency is sporadic. One request might be delayed, but another could get through before the first has a chance to respond.
The solution is relatively simple - use a global array instead of a single global variable, then store each new xmlhttp object in it. The xmlhttpChange function just needs to loop through the array to find what requests are in the ready state.
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; 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) { // 200 OK xmlreqs.splice(i,1); i--; handle_response(); } else { // error xmlreqs.splice(i,1); i--; } } } }
Update: Thanks to Chris Denham for a few changes. In xmlhttpChange the splice occurs on an error as well, and after the splice i is decremented since we‘ve just removed the current array item. He also mentioned that since the xmlreq is added after we send the request, a response might come back before that happens. I think it‘s safe, though I haven‘t measured what sort of time it takes to add the object to the array - if your network latency is really low and your CPU is slow, that might happen and you‘d want to reverse the order.
Update: I moved the xmlreqs.splice(i,1); line above handle_response(); because it creates some interesting results if you initiate a new request from handle_response. Firefox gave a ‘too much recursion‘ error, and in some cases spit out some nice ascii art as well. You‘ll need to retrieve your response from the request before you splice it (using the code below, for example).
In this example I use a class to hold the xmlhttp object. The type variable doesn‘t do anything here, but could hold any sort of info (plus you can add whatever you like to the CXMLReq class). You could also just store the xmlhttp objects in the array, which would work equally well.
The first line in the xmlhttpChange function makes sure that the xmlreqs array exists before doing anything with it. I ran into an error there when sending requests in an onUnload event - the script gets unloaded and takes xmlreqs with it, but the xmlhttpChange function remains attached to the XMLHTTPRequest object until it receives a reply. The object we used is spliced out of the array before we handle the response.
Normally data from the response is accessed from other functions through the global xmlhttp object. Since we‘ve removed that, there are a few options for accessing responses. One way is to associate some data with the xmlhttp object so that we can loop through the array and find the object we want. Another (and the one I prefer) is to copy the xmlhttp.responseXML to a new xml object, and then pass that to your callback function. For example, you can do this in the above sample instead of handle_response():
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;
Now you‘re free to pass xmldoc to your callback function and discard the xmlhttp object.

This method provides an easy way to handle multiple request objects without collisions in an environment where many requests are made simultaneously or when network latency is unstable.

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多