PermaLinkAn Improvement To The Classic Ajax Coding Pattern
02:15:51 PM

Update: There's now a follow-up to this article: A Further Improvement To The Ajax Coding Pattern


A widely-cited article about Ajax from earlier this year shows this coding pattern:


var req;

function loadXMLDoc(url) 
{
    // branch for native XMLHttpRequest object
    if (window.XMLHttpRequest) {
        req = new XMLHttpRequest();
        req.onreadystatechange = processReqChange;
        req.open("GET", url, true);
        req.send(null);
    // branch for IE/Windows ActiveX version
    } else if (window.ActiveXObject) {
        req = new ActiveXObject("Microsoft.XMLHTTP");
        if (req) {
            req.onreadystatechange = processReqChange;
            req.open("GET", url, true);
            req.send();
        }
    }
}


function processReqChange() 
{
    // only if req shows "complete"
    if (req.readyState == 4) {
        // only if "OK"
        if (req.status == 200) {
            // ...processing statements go here...
        } else {
            alert("There was a problem retrieving 
               the XML data:\n" + req.statusText);
        }
    }
}


The pattern did not originate in the above article. In fact, the article links to Apple developer documentation which has the same pattern. It's an adequate pattern for a lot of purposes -- probably for most purposes, actually, but there is a problem with it. Note the use of a global variable req. The "A" in "Ajax" stands for "Asynchronous". It's not quite CS-101, maybe more like 201, but global variables and asynchronous code don't mix well.


This struck me as odd as soon as I saw it, but all the little Ajax applications that I saw using the pattern seemed to work, so at first I figured I was just being my usual self and over-thinking. Eventually, though, I convinced myself that there really was a problem. The remainder of this article describes the problem and my solution.


I began to realize that the global variable really was a problem while continuing my investigation into the implementation of private-key encryption with Ajax. One of the things I investigated (with little hope of actual success) was whether Ajax provided away around restrictions in browsers that prevent code downloaded from one site from accessing data downloaded from another site. The results were negative, which is almost certainly a good thing even though it means that one can't cache a private key retrieved from one server and use it to read encrypted data downloaded from another server. In the investigation, however, I did write code that attempted to make two xmlHttpRequest calls, and when it failed I decided that I really had to prove that it was security restrictions, not some stupid programming error that was getting in the way, so I modified the code to make both xmlHttpRequest calls to the same server, and that didn't work either -- though in a different way and for a different reason. I went on to further tests that definitively proved that the security restriction was coming into play, but I also made a mental note to myself to come back to the question of why only one of my two xmlHttpRequest calls was returning data.


Once I turned my attention to it, it didn't take me long to see the global req variable. A couple of days ago, I was discussing Ajax with Andrew Pollack, and he mentioned the fact that too many programmers learn Javascript by copying others' code, rather than by learning the language. He mentioned this in his own article about Ajax, too, by the way. The use of a global variable to store the xmlHttpRequest object is a perfect case in point where copying code may very well get you into big trouble if you start extending code that you have copied but haven't really taken the time to understand.


By this point, many of you have undoubtedly jumped ahead of me and realized exactly why you shouldn't use a global here. It works fine if you never do two http data retrievals at the same time, but if you are using Ajax your "back-end" code (the processReqChange(), which is a callback function) is asynchronously responding to events generated by the xmlHttpRequest object while simulataneously your "front-end" code is potentially responding to UI events, so in reality you're not necessarily in control of how many data retrievals are going on simultaneously! If you're not doing anything to stop it from happening, a user could very easily click a button that initiates one request, and then quickly click another button that initiates another one. What happens to the first request? I'm not actually 100% sure, but I think it is immediately canceled when the first instantiation of xmlHttpObject is effectively orphaned by the second execution of new XMLHttpRequest();. That would be the better, or at least more predictable outcome, anyhow, because the alternative is probably that the first request completes but is processed as if it were the second request. If you've gone and put more logic into your processReqChange() to take different actions for each of the two requests, and if you've naively followed the pattern and used a global variable to control branching between the two actions, you're going to have some big surprises on your hands.


If only the processReqChange() function were passed an argument that pointed to the xmlHttpRequest object, then the solution would be fairly obvious. If, that is, the first xmlHttpRequest object isn't garbage-collected when the one and only reference to it in the global req variable is overwritten. So, what, then is the correct pattern? The best, most bullet-proof patten? That I don't have. Sorry! Not yet, anyhow. It will probably have to involve a dynamic array of objects containing xmlHttpRequest objects and any other associated flags needed to control processing of results. That will have to be a future project. For now, I'm settling for a pattern that cleanly handles two different xmlHttpRequests, easily extensible to a pattern for any fixed number of requests.


Here's a link to a re-written version of my test app that pulls multiple RSS feeds into one page. Now don't go thinking this is going to look pretty, by the way. I don't do anything with those RSS feeds beside just display their raw XML. It's the code that matters.


<script language='javascript'>
<!--


function initXMLHttpRequest() {

                                                                          // branch for native XMLHttpRequest object
    if (window.XMLHttpRequest) {
        req = new XMLHttpRequest();
     }
     else if (window.ActiveXObject) {      // branch for IE/Windows ActiveX version
        req = new ActiveXObject("Microsoft.XMLHTTP");
     }

  return(req);

}

var req1 = initXMLHttpRequest();
var req2 = initXMLHttpRequest();

function loadXMLDoc(url,req, handler) 
{
        req.onreadystatechange = handler;
        req.open("GET", url, true);
        if (window.XMLHttpRequest) {
    req.send(null);
        }
        else {
                req.send();
        }    
        window.document.all.status.style.color = "green";
        window.document.all.status.innerHTML = "Reading Feed";
}


function handler1() {
   var theXML = processReadyStateChange(req1)
   window.document.ajaxForm.feed1.value = theXML;
}


function handler2() {
   var theXML = processReadyStateChange(req2);
   window.document.ajaxForm.feed2.value = theXML;
}


function processReadyStateChange(req) 
{

    // only if req shows "complete"
    if (req.readyState == 4) {
        // only if "OK"
        if (req.status == 200) {
          // ...processing statements go here
        return req.responseText;
        } else {
    window.document.all.status.style.color = "red";
                window.document.all.status.innerHTML = "There was a problem retrieving the RSS feed\n" + req.statusText;
        }
    }
}


function getFeed1() {

    loadXMLDoc(window.document.ajaxForm.url1.value, req1, handler1)

}

function getFeed2() {

    loadXMLDoc(window.document.ajaxForm.url2.value, req2, handler2)

}

function displayFeed( theFeed, target ) {

    target.value = theFeed;

}


-->
</script>


Let's look at what I've done. There are two global variables now, req1 and req2. Each is initialized by a call to initXmlHttpRequest() rather than within the loadXMLDoc() function. The initialized xmlHttpObject is passed into loadXMLDoc(). Also passed in is a handler function, which is either handler1() or handler2(). The code in loadXMLDoc() sets req.onreadystatechange to the passed-in handler function instead of to processReadyStateChange. (BTW: I changed the name processReqChange to processReadyStateChange just because I'm feeling ornery. It isn't the req that changes. It's the state of the req that changes to "ready".) The functions handler1() and handler2() each call processReadyStateChange(), and they pass in the appropriate req as an argument to it. This is my way around the fact that the callback isn't passed a reference to the xmlHttpRequest object. The handler functions receive the XML as a return value from processReadyStateChange(), which keeps that function generic. (You may want different versions of it depending on whether you want the reqeustText or requestXML passed back.) The handler functions then take care of processing the XML, in this case simply writing it to an input element -- but it could just as easily have done a transformation and updated innerHTML for a DIV.


Well, that's the pattern. It's heavier in code than the pattern made popular by the article referenced above, but it is a little more robust and also IMHO quite a bit more readable. If you look at the code behind something like Google Maps you'll see that they're not doing it my way. They're also not following the pattern that I'm trying to improve on. To be honest, I only spent a few minutes looking at the code though, and I don't know much about what they're doing. It's not written with readability in mind, that's for sure. I suspect, however, that they are in fact managing a dynamic list of xmlHttpRequest objects, which is what is really needed for truly dynamic interfaces that respond predictably where users are clicking all over the place without giving one operation a chance to finish before the next click.


Other articles in this series...

    Is Ajax The Answer For Crypto In Browser-Based Applications
    Proof of Concept: Browser-Based Field Encryption With Blowfish Via Ajax
    The Obstacles To Public Key Encryption In The browser
    A Further Improvement To The Ajax Coding Pattern
    Why Ajax And Crypto Intrigue Me

This page has been accessed 2021 times. .
Comments :v

1. Alan Bell05/14/2005 07:31:41 PM
Homepage: http://www.dominux.co.uk


do you have a copy of Principals of Concurrent and Distributed programming by Ben-Ari? I suspect the clean solution is in there somewhere. If you don't have it then I will pop it in my bag next week.

Alan.




2. Stephan H. Wissel05/14/2005 08:18:27 PM
Homepage: http://www.wissel.net


Well spoken and well explained!
Have you checked out SF's sarissa project. Eventually they have taken care of that already?
stw




3. Richard Schwartz05/14/2005 08:27:48 PM
Homepage: http://smokey.rhs.com/web/blog/PowerOfTheSchwartz.nsf


@Alan: No, I don't have that one. I did study the subject a long, long time ago, but most of what I remember involves things like semaphors and critical sections, and I certainly don't expect that level of control of concurrency in Javascript.

@Stephan: I'm not familiar with that project, but I'll have a look.

-rich




4. John Wehr05/15/2005 08:25:51 PM
Homepage: http://www.johnwehr.com


XMLHttpRequests = new Array();
XMLHttpRequest_callbacks = new Array();
XMLHttpRequest_count = 0;

function runcallback(i) {
if (XMLHttpRequests[i].readyState == 4) {
if( self[ XMLHttpRequest_callbacks[i] ] ) {
self[ XMLHttpRequest_callbacks[i] ]( XMLHttpRequests[i].responseXML );
} else {
eval('f = function() { runcallback(' + i + '); }');
setTimeout( f, 100 );
}
XMLHttpRequest_count -= 1;
}
}

function loadXMLDoc( url, c, postvars ) {

if (window.XMLHttpRequest) {
XMLHttpRequests.push( new XMLHttpRequest() );
} else if (window.ActiveXObject) {
XMLHttpRequests.push( new ActiveXObject('Microsoft.XMLHTTP') );
}

XMLHttpRequest_callbacks.push( c );

i = XMLHttpRequests.length - 1;

eval('f = function() { runcallback(' + i + '); }');

XMLHttpRequests[i].onreadystatechange = f;

if( !postvars ) {
XMLHttpRequests[i].open('GET', url, true);
XMLHttpRequests[i].send( null );
} else {

pv = "";

for( key in postvars ) {
pv += '&' + key + '=' + postvars[key];
}

XMLHttpRequests[i].open('POST', url, true);
XMLHttpRequests[i].send( pv.substring(1) );
}

XMLHttpRequest_count += 1;

}

function handler( xml ) {
////
}

/*

Then do something like
loadXMLDoc('rest.php?mode=info', 'handler' );

btw - what did you use to format your code for display on this page?

*/




5. Richard Schwartz05/15/2005 10:15:35 PM
Homepage: http://smokey.rhs.com/web/blog/PowerOfTheSchwartz.nsf


Very cool code, John. Thanks. Definitely worthy of consideration as the Ultimate Ajax Pattern.

The formatter I used is here: http://www.beginthread.com/Article/Ehsan/Code%20Formatter/

It uses font tags, not CSS, but other than that it seems to do a very nice job. In fact, I just posted a formatted version of your code here: http://smokey.rhs.com/web/blog/poweroftheschwartz.nsf/htdocs/RSCZ6CF4HE.htm

-rich




6. Richard Schwartz05/15/2005 10:21:00 PM
Homepage: http://smokey.rhs.com/web/blog/PowerOfTheSchwartz.nsf


I should mention that the formatter does not do indentation. I had to do that by hand.

-rich




7. Kris Brixon05/30/2005 04:12:25 PM
Homepage: http://www.brixon.org


here a version that I wrote up before I saw John's response. I guess mine would be considered a little less elegant, but I rarely use JavaScript.

var aQueueDivID = new Array();
var aQueueURL = new Array();

// Processing Flag - 0-False 1-True
bProcessing = 0;

// Counter of current request
iCurrentRequest = 0;

// function the user calls.
function loadDiv(url,divid) {
aQueueDivID[aQueueDivID.length] = divid;
aQueueURL[aQueueURL.length] = url;

// first request or if all requests are processed
if (bProcessing == 0) {
nextRequest();
}
}

function nextRequest() {
bProcessing = 1;
iCurrentRequest = iCurrentRequest + 1;

// branch for native XMLHttpRequest object
if (window.XMLHttpRequest) {
req = new XMLHttpRequest();
req.onreadystatechange = processReqChange;
req.open("GET", aQueueURL[iCurrentRequest-1], true);
req.send(null);

// branch for IE/Windows ActiveX version
} else if (window.ActiveXObject) {
req = new ActiveXObject("Microsoft.XMLHTTP");

if (req) {
req.onreadystatechange = processReqChange;
req.open("GET", aQueueURL[iCurrentRequest-1], true);
req.send();
}
}

}

function processReqChange() {
// readyState - 4 = Page is Complete
if (req.readyState == 4) {
if (req.status == 200) {
document.getElementById(aQueueDivID[iCurrentRequest-1]).innerHTML = req.responseText;
}
bProcessing = 0;
if (aQueueURL.length > iCurrentRequest) {
// the current request is complete so run the next request that is in the array.
nextRequest();
}
}
}

-Kris




8. JaGx08/10/2005 08:15:16 AM
Homepage: http://pimpsofpain.com


WAHH?
The solution is clear if YOU learned javascript instead of copying others' coding

Using function nesting - you can do something like this:

function start()
{
for (SOME LOOP THROUGH TEH DATA)
{
(function (a,b,c,d,e,ETC){ //pass the variables we want to remain constant regardless of the looping as params
var req = getxmlhttprequestobject(); //get our object inside this function - we are in our anonymous function so it CANNOT be overwritten by another httprequest object created by the loop
req.onreadystatechange = function (){DO SOME ANALYZING OF THE DATA HERE}
req.open(PARAMS)
req.send(POSTDATA);
})(); //execute our anonymous function
}
}

start();


There my friends, is the solution. Object relativity to avoid overwriting variables




9. vesoftware11/05/2013 10:15:55 PM
Homepage: http://vesoftware.blogspot.com/2013/10/itupokercom-agen-poker-online-indonesia.html


Agen Bola Promo 100% SBOBET IBCBET Casino Poker Tangkas Online
http://vesoftware.blogspot.com/2013/10/agen-bola-promo-100-sbobet-ibcbet.html
ITUPOKER.COM AGEN POKER ONLINE INDONESIA TERPERCAYA
http://vesoftware.blogspot.com/2013/10/itupokercom-agen-poker-online-indonesia.html
alfaonline.com : Toko belanja online murah, Promo heboh jual barang hanya Rp 1,-
http://vesoftware.blogspot.com/2013/10/alfaonlinecom-toko-belanja-online-murah.html
BELAJAR SEO
http://balapseo.blogspot.com/




Enter Comments^


Email addresses provided are not made available on this site.





You can use UUB Code in your posts.

[b]bold[/b]  [i]italic[/i]  [u]underline[/u]  [s]strikethrough[/s]

URL's will be automatically converted to Links


:-x :cry: :laugh: :-( :cool: :huh: :-) :angry: :-D ;-) :-p :grin: :rolleyes: :-\ :emb: :lips: :-o
bold italic underline Strikethrough





Remember me    

Monthly Archive
Responses Elsewhere



About The Schwartz

rss.jpg


All opinions expressed here are my own, and do not represent positions of my employer.