Situation: In an AJAX application the user can click additional AJAX requests before the first finish. Client requires only the results for the last request display.
What happens when an XHR call is made before a previous call is finished using the same XHR object?
Of course, this depends on the requirements and how the AJAX is coded. There are three likely behaviors from which to choose.
Below is a quick example. Click the 4 calculations as fast as you can. This may not be fast enough to cause the request to collide so the "calculate" button is provided. It will simulate rapidly clicking each calculation.
Single global XHR object | XHR Threads |
---|---|
|
|
To achieve our goal of only completing the last AJAX request, a single global XHR object is used. Three functions—not counting the button functions—are needed: createXHR, runAjax, and showReturnedData.
createXHR is the key to meeting the requirement. The standard function for creating an instance of the xmlHttpRequest object described in Ajax in a Nutshell: Create an xmlHttpRequest (XHR) Object is modified as shown below.
var xhr = null; function createXHR(xhrObj) { if (xhrObj && xhrObj.readyState < 4 && browser.isGecko) { xhrObj.abort(); //needed for FireFox xhrObj = null; //needed for FireFox } if (!xhrObj) { if (window.XMLHttpRequest) { // branch for native XMLHttpRequest object - Mozilla try { xhrObj = new XMLHttpRequest(); } catch (e) { xhrObj = null; } } else if (window.createRequest) { /* For ICEbrowser -- untested. * per their site * http://support.icesoft.com/jive/entry.jspa?entryID=471&categoryID=21 * There are a number of restrictions on the implementation. */ try { xhrObj = window.createRequest(); } catch (e) { xhrObj = null; } } else if (window.ActiveXObject) { // branch for IE/Windows ActiveX version try { xhrObj = new ActiveXObject("Msxml2.XMLHTTP"); } catch(e) { try{ xhrObj = new ActiveXObject("Microsoft.XMLHTTP"); } catch(e) { xhrObj = null; } }//catch } //if-else } return xhrObj; }
For most browsers, calling the open method of the XHR object will abort any current call and make the object ready to use. However, in the case of Firefox an error is thrown so it is necessary check and abort any current process.
Everytime an AJAX call is made createXHR will be call. It checks if the XHR object exists, if the object's readyState is less than 4 (completed), and if the browser is Firefox or a Firefox clone. Under those conditions the process is aborted and the XHR object is set to null so it will be created again. All of this is for Firefox. Refer to Browser Sniffing for the browser.isGecko variable.
Because of what appears to be a timing issue in Firefox, it is necessary to replace the XHR object with a new instance. It appears that a small amount of time is required for the abort process to complete and the open method gets called before the time has elapsed.
Firefox requires the abort and recreating
the XHR object. IE 6, on the otherhand, should not have the abort.
It fires a readystatechange with a readyState
of 4 (completed) and a status value of 0. IE 7 and Safari do not
signal a completed process so they work with or without the abort.
It is important the onreadystatechange handler be assigned
AFTER the open call.
runAjax is attached to the triggering event...onsubmit for a form or onclick for a like. For demonstration of the concept, the handler is attached in-line, but this can be adapted to any method of binding handlers.
function runAjax(url, callback, targId) { xhr = createXHR(xhr); if (xhr) { try { callback.targId = targId; xhr.open("GET", url, true); xhr.onreadystatechange = callback; xhr.send(null); } catch (e) { //Handle Error } } } //eof runAjax
The only difference between this function and the ones used in Thread/Concurrent AJAX Calls or Ajax in a Nutshell: Create an xmlHttpRequest (XHR) Object is that the variable xhr is global (no var in the declaration).
The order of the open method call and setting the onreadystate event is important in IE. When calling open aborts a previous process, calling open clears/resets the object's properties having the effect of clearing the onreadystate event handler.
Each runAjax is called it calls createXHR which checks the status of the global XHR object and clears it if necessary.
Note that the ID of target element for the return data is assigned to a property of the callback function. This avoids using another global variable or a closure—your choice.
Last, a function is needed to respond when the XHR call is completed. This is called a callback function.
function showReturnedData() { var targ; if (xhr.readyState == 4) { try { if (xhr.status == 200) { var targ = document.getElementById(showReturnedData.targId); targ.innerHTML = xhr.responseText; } else { alert("Status error"); } } catch (e) { alert("bad status"); } } }//eof show
The try catch is required for Firefox which throws an error when the server doesn't return a status.