Sunday, August 20, 2006

ajax gotcha for new players

Being fairly new to the "Brave New World" of ajax, this week I fell into a fairly common trap that gets new players. I studiously looked around for the best practice implementation of making an http request using javascript and came up with the following javascript function which allows the user to make the http request, specify a callback function, and even goes the extra mile of implimenting timeouts (I guess the only other thing I really should do is implement an OnErrorCallback).


function MakeRequest(url, timeout,
onCompleteCallback, onTimeoutCallback)
{
var completeCallback = onCompleteCallback;
var timeoutCallback = onTimeoutCallback;

var xmlHttp=createXMLHttpRequest();

if (xmlHttp)
{
xmlHttp.onreadystatechange = function()
{
if (xmlHttp.readyState == 4)
{
window.clearTimeout(timeoutId);
if (xmlHttp.status == 200 ||
xmlHttp.status == 304)
{
completeCallback(
xmlHttp.responseText);
}
}
};

xmlHttp.open("GET",url,true);

var timeoutId =
window.setTimeout(function()
{
if (callInProgress(xmlHttp))
{
xmlHttp.abort();
timeoutCallback('timeout');
}
} , timeout);

xmlHttp.send(null);
}
}

function callInProgress(xmlHttp)
{
switch ( xmlHttp.readyState )
{
case 1, 2, 3:
return true;
break;

// Case 4 and 0
default:
return false;
break;
}
}


function createXMLHttpRequest() {
if(window.XMLHttpRequest) {
try {
xmlHttpRequest = new XMLHttpRequest();
} catch(e) { return null; }
} else if(window.ActiveXObject) {
try {
xmlHttpRequest =
new ActiveXObject("Msxml2.XMLHTTP");
} catch(e) {
try {
xmlHttpRequest =
new ActiveXObject("Microsoft.XMLHTTP");
} catch (e) { return null; }
}
} else return null;
return xmlHttpRequest;
}


I then wanted to call a .Net HttpHandler, and lets just say for arguments sake that the htp handler looks like this


public class Handler : IHttpHandler
{

public void ProcessRequest (HttpContext context)
{
System.Threading.Thread.Sleep(6000);
context.Response.ContentType = "text/plain";
context.Response.Write(Guid.NewGuid().ToString());
}

public bool IsReusable
{
get
{
return false;
}
}
}


so the call from javascript would simply look something like this



MakeRequest(
'Handler.ashx?cookie=abcd&another=xyz',
20000, onRequestComplete,
onRequestTimeout);

function onRequestTimeout(response)
{
alert('timeout');
}

function onRequestComplete(response)
{
alert(response);
}



The problem as I found out through good old wikipedia relates to the internet explorers caching of the "GET" http command as described in this article on HMLHttpRequest.
As discussed in the article, there are a number of ways around this, switch to using POST or ensure that you switch the caching off in the http headers. I chose the former.

4 comments:

  1. Ed Purkiss5:53 am

    Hello Scott -

    I'm an AJAXr from Phoenix Arizona. Have you ever seen inconsistent responses from the MS XMLHTTP object? I have run side-by-side tests of FF and IE on the same machine and often see the XMLHTTP object do the normal two pops of readystatechange with 1 and 1, then hang right there - never moving on to 2/3/4 - while the FF object just keeps chugging along...(ensuring network stability @ time of error) have you ever seen anything like that, and if so, what did you do to work around?

    Thanks,
    -EP

    ReplyDelete
  2. No, sorry ed, I haven't seen that one, although I do know that the FF and ie6 XMLHttpRequest objects do behave slightly differently, I just wish the W3C would hurry up and release a standard for it, then we could get back to situation normal, that is blaming Microsoft for not implementing the standard, (doesn't get us any further, just provides a vehicle for our rage). Having said that, it was Microsoft that first introduced XMLHttpRequest back in ie5.

    Might be worth having a look at ie7, they have moved away from using the ActiveX control to a similar model to FF, they may have made other improvements too.

    Cheers.

    ReplyDelete
  3. Ed Purkiss (ed at me3inc.com)9:27 am

    Thanks for the response Scott -

    Unfortunately, the app I working on is for severeal clients and I have no say in the browser they are using. However, wrapping the object in a better timeout procedure as well as a couple little extra gotchas I worked through has made things a lot more stable. Just for your future ref, here are a couple things I did:
    * Clear the onreadystatechange property BEFORE I call Abort - this handles an issue where the object would sometimes post a ready state of 4 when I abort()ed
    * Reuse the object if it is FF et al, null and reinstantiate if it is IE
    * Clear my timeout if I receive a readystate of 2 or greater - my problem was after the readystate 1 it would just hang - but timing wise, I got some times where the timeout popped (and aborted the request) while I was mid-process if I waited all the way to rs=4!
    * Try the XMLHttpRequest first, msxml2 then the old IE object in that order - ie7 will use the new object (preferable) and the code degrades nicely into lesser versions of IE.

    Cheers,
    -EP

    ReplyDelete
  4. Thanks for this tip - my page was hanging on a ajax request in IE. I appended the URL with a random number to get around the HTTPGET caching issue.

    ReplyDelete