JavaScript: Avoiding the Race Condition with AjaxThe main problem with the code we presented earlier is that it didn't take into account the possibility of multiple XMLHttpRequests running concurrently and therefore falling vicitim to the race condition which is a common occurence in asynchronous systems. The problem is that the single global variable req is overwritten as soon as another call is made to the loadXMLDoc() function, effectively terminating any previous calls that have not yet reached the execution state (the processReqChange() function). Introducing the AjaxRequest classIt's actually a trivial exercise to transform the previous code to use a class and no longer rely on global variables. The modified code is shown here: // Original JavaScript code by Chirp Internet: www.chirp.com.au
// Please acknowledge use of this code by including this header.
function AjaxRequest()
{
var req;
var method = "GET";
var nocache = false;
this.loadXMLDoc = function(url, params)
{
if(window.XMLHttpRequest) {
try {
req = new XMLHttpRequest();
} catch(e) {
req = false;
}
} else if(window.ActiveXObject) {
try {
req = new ActiveXObject("Msxml2.XMLHTTP");
} catch(e) {
try {
req = new ActiveXObject("Microsoft.XMLHTTP");
} catch(e) {
req = false;
}
}
}
if(req) {
req.onreadystatechange = processReqChange;
if(nocache) {
params += (params != '') ? '&' + (new Date()).getTime() : (new Date()).getTime();
}
if(method == "POST") {
req.open("POST", url, true);
req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
req.send(params);
} else {
req.open(method, url + '?' + params, true);
req.send(null);
}
return true;
}
return false;
}
this.setMethod = function(newmethod) { method = newmethod.toUpperCase(); }
this.nocache = function() { nocache = true; }
// define private methods
var getNodeValue = function(parent, tagName)
{
var node = parent.getElementsByTagName(tagName)[0];
return (node && node.firstChild) ? node.firstChild.nodeValue : '';
}
var processReqChange = function()
{
if(req.readyState == 4 && req.status == 200) {
var response = req.responseXML.documentElement;
var commands = response.getElementsByTagName('command');
for(var i=0; i < commands.length; i++) {
method = commands[i].getAttribute('method');
switch(method) {
case 'alert':
var message = getNodeValue(commands[i], 'message');
window.alert(message);
break;
case 'setvalue':
var target = getNodeValue(commands[i], 'target');
var value = getNodeValue(commands[i], 'value');
if(target && value != null) {
document.getElementById(target).value = value;
}
break;
case 'setdefault':
var target = getNodeValue(commands[i], 'target');
if(target) {
document.getElementById(target).value = document.getElementById(target).defaultValue;
}
break;
case 'focus':
var target = getNodeValue(commands[i], 'target');
if(target) {
document.getElementById(target).focus();
}
break;
case 'setcontent':
var target = getNodeValue(commands[i], 'target');
var content = getNodeValue(commands[i], 'content');
if(target && content != null) {
document.getElementById(target).innerHTML = content;
}
break;
case 'setstyle':
var target = getNodeValue(commands[i], 'target');
var property = getNodeValue(commands[i], 'property');
var value = getNodeValue(commands[i], 'value');
if(target && property && value) {
document.getElementById(target).style[property] = value;
}
break;
case 'setproperty':
var target = getNodeValue(commands[i], 'target');
var property = getNodeValue(commands[i], 'property');
var value = getNodeValue(commands[i], 'value');
if(value == "true") value = true;
if(value == "false") value = false;
if(target) {
document.getElementById(target)[property] = value;
}
break;
default:
window.console.log("Error: unrecognised method '" + method + "' in processReqChange()");
}
}
}
}
}
You can download this class as ajaxrequest.js. How does this affect my code?Old VersionWith the old code you would call the loadXMLDoc() function directly: <script type="text/javascript" src="xmlhttp.js"></script>
<script type="text/javascript">
function callAjax(method, value, target)
{
var params = "method=" + method + "&value=" + encodeURIComponent(value) + "&target=" + target;
loadXMLDoc("/scripts/validate.php", params);
return true;
}
</script>
New VersionWith the new code you first need to instantiate an AjaxRequest object and then call it's loadXMLDoc() function. The relevant changes are highlighted in the code below: <script type="text/javascript" src="ajaxrequest.js"></script>
<script type="text/javascript">
function callAjax(method, value, target)
{
var req = new AjaxRequest();
var params = "method=" + method + "&value=" + encodeURIComponent(value) + "&target=" + target;
req.loadXMLDoc("/scripts/validate.php", params);
return true;
}
</script>
The old code will still work perfectly for simple applications where only one request can be made at a time, but in more complex systems - relating to form validation for example - you should definately be using the AjaxRequest class instead. Extra FunctionalityTwo additions to the code that you might want to know about are the ability to set the request method to POST (or HEAD or any other valid method) and the ability to disable caching as described in the previous article. To set the request method to POST for example: var req = new AjaxRequest();
req.setMethod("POST");
req.loadXMLDoc(url, params);
And to disable caching for GET requests: var req = new AjaxRequest();
req.nocache();
req.loadXMLDoc(url, params);
Note: If you convert the call from GET to POST don't forget to also change the server-side script to accept POST variables. Hiding from older browsersOlder browsers such as MSIE 5.0 do not support encodeURIComponent() and will not be able to run the callAjax() function presented above. To get around this you can either:
Implementing the first option means that you will no longer be able to safely pass UNICODE characters to the server-side script. This could be a problem if you're passing user input rather than just simple variables. The second option involves a slight change to the function: <script type="text/javascript">
function callAjax(method, value, target)
{
if(encodeURIComponent) {
var req = new AjaxRequest();
var params = "method=" + method + "&value=" + encodeURIComponent(value) + "&target=" + target;
req.loadXMLDoc("/scripts/validate.php", params);
}
return true;
}
</script>
Having the condition added around the Ajax code means that it will not be executed in MSIE 5.0 or equivalent browsers that don't support encodeURIComponent(). Related Articles
ReferencesFeedback and Questions23 May 2006: Ian says: Just trying out your Ajax approach for a new site using ASP, but I can't get it working in Firefox on Windows... 19 January 2009: Jeff says: Thanks for the script. I noticed that it pops an error message in IE 6. I was able to fix it by changing this line: if(!req.responseXML) return; Thanks for that Jeff. I don't always have time to test everything properly in Windows - it give me a headache |
|
|
© Copyright 2010 Chirp Internet
- Page Last Modified: 22 November 2009
|
|