The Information Technology Blog brought to you by 4 Ace Technologies

Sunday, May 31, 2009

How to Make AJAX work Fast

Making Ajax Elegantly Fast

Sunday, September 30th, 2007

Sometime ago I was messing around with various techniques for ways that we can optimize asynchronous calls to the server via XMLHttpRequest (and the ActiveX equivalent of XMLHTTP). I wrote about one of those techniques known as branching in an article for Digital Web Magazine called Seven JavaScript Techniques You Should Be Using Today back in April, however, there was a flaw in the function that when one request is made before another one is finished, you would run into an error since it is using the same reference to the instance object for the request. That’s cool in theory, but bad in practice. The benefit you get with that is that you never create more than one XMLHttpRequest object for all your requests. This is fine for light weight use, and when you know for sure that you won’t be making frequent requests simultaneously. But when does that ever happen? Really.

Here’s what that code looked like:

Outdated asyncRequest function

var asyncRequest = function() {
function handleReadyState(o, callback) {
var poll = window.setInterval(function() {
if(o && o.readyState == 4) {
window.clearInterval(poll);
if ( callback ){
callback(o);
}
}
},
50);
}
var http;
try {
http = new XMLHttpRequest;
}
catch(e) {
var msxml = [
'MSXML2.XMLHTTP.3.0',
'MSXML2.XMLHTTP',
'Microsoft.XMLHTTP'
];
for ( var i=0, len = msxml.length; i < len; ++i ) {
try {
http = new ActiveXObject(msxml[i]);
break;
}
catch(e) {}
}
}
return function(method, uri, callback, postData) {
http.open(method, uri, true);
handleReadyState(http, callback);
http.send(postData || null);
return http;
};
}();

Note that the function still branches within the body of the closure, and that branch happens only once. The main issue here is that we need another function that essentially branches the way get the XMLHttpRequest object, but without being run multiple times. In other words, this would be bad:

bad example for XMLHttpRequest

function getXHR() {
var http;
try {
http = new XMLHttpRequest;
} catch (ex) {
var msxml = [
'MSXML2.XMLHTTP.3.0',
'MSXML2.XMLHTTP',
'Microsoft.XMLHTTP'
];
for (var i=0, len = msxml.length; i < len; ++i) {
try {
http = new ActiveXObject(msxml[i]);
break;
}
catch(e) {}
}
}
}

The reason this is slow is because every request for a new XMLHttpRequest object we have to run through the same logic upon each call to this function. Instead we can use what Peter Michaux coined as the Lazy Function Definition Pattern, and apply that technique here. Our optimized (and final) code would then look like this:

Optmized Speedy Ajax Code

var asyncRequest = function() {
function handleReadyState(o, callback) {
if (o && o.readyState == 4 && o.status == 200) {
if (callback) {
callback(o);
}
}
}
var getXHR = function() {
var http;
try {
http = new XMLHttpRequest;
getXHR = function() {
return new XMLHttpRequest;
};
}
catch(e) {
var msxml = [
'MSXML2.XMLHTTP.3.0',
'MSXML2.XMLHTTP',
'Microsoft.XMLHTTP'
];
for (var i=0, len = msxml.length; i < len; ++i) {
try {
http = new ActiveXObject(msxml[i]);
getXHR = function() {
return new ActiveXObject(msxml[i]);
};
break;
}
catch(e) {}
}
}
return http;
};
return function(method, uri, callback, postData) {
var http = getXHR();
http.open(method, uri, true);
handleReadyState(http, callback);
http.send(postData || null);
return http;
};
}();

Take closer look at how the getXHR function is redeclared within main declaration to simply return the appropriate object back to the client when requested. And that folks, is Ajax served elegantly fast! Feel free to try it yourself :)

asyncRequest in use

asyncRequest('GET', 'foo.php', function(o) {
console.log(o.responseText);
});

51 Responses to “Making Ajax Elegantly Fast”

  1. Calm_Pear

    Very interesting! Thanks for sharing that!

  2. Mourner

    Dustin, thanks for a great tip!
    It seems that you forgot to make a poll in handleReadyState definition in your updated code, as it would be called only once which is incorrect.

  3. Bhabishya Kumar


    I really liked your technique and so wanted to check if the popular JavaScript libraries are also using it. I just looked at jQuery1.1.4 and it just had a one-liner.



    var xml = window.ActiveXObject ? new ActiveXObject("Microsoft.XMLHTTP") : new XMLHttpRequest();


    Any problem with this?

  4. Graham Bradley

    I noticed that problem could occur with the example you gave, but never really thought it through properly to come up with an alternative. Really nice solution :)

  5. Dustin Diaz

    @Mourner: I removed the poll because that was an outdated piece of code that assumed IE6 leaked memory. Since then IE has fixed that.

    @Bhabishya: Yes, there is… that piece of logic is run upon every request. And not only that, it doesn’t take advantage of IE’s newer methods for XMLHTTP such as MSXML2 and XMLHTTP.3.0.

    @Graham: Yeah. It doesn’t seem to bother a lot of people. But working at Google has tweaked my mind to always think about speed and optimization techniques, hence this blog post :

  6. daniel

    @Graham: Yeah. It doesn’t seem to bother a lot of people. But working at Google has tweaked my mind to always think about speed and optimization techniques, hence this blog post

    yes, thats true
    changing workplaces often changes the thinking to

    ps: nice to read something new on your blog :)

  7. napyfab:blog» Blog Archive » links for 2007-09-30

    [...] Making Ajax Elegantly Fast (tags: ajax javascript fast web development webdev) This entry was written by delicious and posted on 1 Ottobre 2007 at 01:25 and filed under Del.icio.us. Bookmark the permalink. Follow any comments here with the RSS feed for this post. Post a comment or leave a trackback: Trackback URL. [...]

  8. Andrew Mattie

    Hey Dustin, thanks for the informative article. I’m left with three questions though that I hope you’ll be able to shed some light on.

    First, not that it’s a big deal, but what’s the point of the ‘return http;’ line in your original getXHR function? Since you are redefining the function anyway, it doesn’t seem like it would serve a purpose. However, since that wasn’t in your original writeup, I’m assuming you must have added it for a reason.

    Second, why don’t you test for XMLHttpRequest using a !! flag instead of a try / catch block? While the ‘catch’ block for the way you’re doing it would only get hit once in IE6, that’s once more than it needs to be hit it seems. The performance gain would probably be just a few milliseconds or so, and it’s maybe not even worth discussing, but I figured I’d ask anyway.

    Finally, is there a reason you chose to use Michaux’s pattern when you rewrote this instead of your original pattern based on function closures (not sure what else to call it really). For example, is there a reason you did this


    var asyncRequest = function() {
    ...
    var getXHR = function() {
    ...
    if (!!XMLHttpRequest)
    getXHR = function() { return new XMLHttpRequest(); }
    ...
    }
    ...
    }

    instead of this


    var asyncRequest = function() {
    ...
    var getXHR = function() {
    ...
    if (!!XMLHttpRequest)
    return function() { return new XMLHttpRequest(); }
    ...
    }();
    ...
    }

    ? As some who wrote a book on the subject of JavaScript patterns, I’m especially curious to hear more about this one from you.

    I’m honestly not trying to knock your example, nit-pick at things, or say anything you did was wrong. I’m just really curious to learn more about why you did what you did.

  9. Dustin Diaz

    @Andrew:

    1) http is returned so you always have the object incase you want to abort or grab other properties. In other words… you can do this…

    var requestObject = asyncRequest('GET', 'foo.php', function(o) { });
    ...
    requestObject.abort();

    2) and 3) Actually, I don’t know why I didn’t use my usual closure technique (which is always my favorite). We can take it a step further and use a ternary operator to decide which function is assigned to getXHR…

    var getXHR = function() {
    return !!XMLHttpRequest ?
    function() {
    return function() { return new XMLHttpRequest; }
    } :
    (function() {
    var msxml = [ ... ];
    for ( ... ) {
    try {
    ...
    } catch (ex) { }
    })();
    }();
  10. js fan

    Something I do not understand.
    1. You remove the poll in the handleReadyState function, and you say it may cause the ie to leak memory, but without the poll the asyncRequest can not get any response, have you test it by yourself this function?
    2. Code problem:


    var msxml = [
    ‘MSXML2.XMLHTTP.3.0′,
    ‘MSXML2.XMLHTTP’,
    ‘Microsoft.XMLHTTP’
    ];

    it is:


    var msxml = [
    'MSXML2.XMLHTTP.3.0',
    'MSXML2.XMLHTTP',
    'Microsoft.XMLHTTP'
    ];

    3. I do not like your lazy-like getXHR function implementation, below is my implementation:


    var getXHR= function(){
    if(window.XMLHttpRequest)
    return function(){return new XMLHttpRequest}
    else if(window.ActiveXObject)
    return function(){
    var http;
    var msxml = [
    'MSXML2.XMLHTTP.3.0',
    'MSXML2.XMLHTTP',
    'Microsoft.XMLHTTP'
    ];
    for (var i=0, len = msxml.length; i

    I believe it is much more reasonable and readable, and maybe this optimization is only for the stupid internet explorer 6.
    4. You add checking the status 200 in the handleReadyState function, but what about the local file system, and the file protocol, maybe “o && o.readyState == 4 && (o.status == 200 || o.status == 0)” would be better.
    Last: this blog is not quite convenient, First time I do not input the e-mail address and it tell me an error, and I returned to this page, and the the textarea has been cleared……. Sorry for posting again, I find last post has some problem please delete it, and this blog lack of the preview feature.

  11. js fan

    And this is my getXHR function:


    var getXHR= function(){
    if(window.XMLHttpRequest)
    return function(){return new XMLHttpRequest}
    else if(window.ActiveXObject)
    return function(){
    var http;
    var msxml = [
    'MSXML2.XMLHTTP.3.0',
    'MSXML2.XMLHTTP',
    'Microsoft.XMLHTTP'
    ];
    for (var i=0, len = msxml.length; i < len; ++i) {
    try {
    http = new ActiveXObject(msxml[i]);
    getXHR = function() {
    return new ActiveXObject(msxml[i]);
    };
    break;
    }
    catch(e) {}
    }
    return http;
    }
    }();

    Sorry for posting again, the HTML entities………….

  12. kentaromiura

    I use often this technique,
    but be careful because if you use closures AND you get a Dom node in the factory function you leech some memory on IE.
    In your case the problem don’t exists.

  13. Jonathan Snook

    For clarification, MSXML2.XMLHTTP.3.0, MSXML2.XMLHTTP, and Microsoft.XMLHTTP all map to the same object internally: “MSXML2.XMLHTTP.3.0″. (that is, on any currently supported Internet Explorer platform from Windows 2000 and up) Checking for each version as you are is unnecessary. The one version you should add is “Msxml2.XMLHTTP.6.0″ which is the version that ships with IE7 and would be used if somebody turned off the native XHR object (not a likely scenario but seems like the best way to handle it).

  14. kentaromiura

    @Snook:
    xmlhttp 6.0 can be installed as stand-alone so even an IE6 machine could use it.

    there is a urban legend out there that say that one of the 2 {MSXML2.XMLHTTP, and Microsoft.XMLHTTP} map to the LAST msxml installed on a machine, i don’t know if this is true.
    For what I know microsoft.* is the OLD namespace and MSXML2 the new one.

  15. Web Development News 10.01.07 | Matt Snider JavaScript Resource

    [...] http://www.dustindiaz.com/faster-ajax/ [...]

  16. Calm_Pear

    @Dustin > @Andrew
    I think both implementations work but for readability I prefer Dustins version…

    And about the namespaces, the MS XML Team has this to say:
    http://blogs.msdn.com/xmlteam/archive/2006/10/23/using-the-right-version-of-msxml-in-internet-explorer.aspx

    Which apparently results in using only: Msxml2.DOMDocument.6.0 and Msxml2.DOMDocument.3.0

  17. Calm_Pear

    crap… those are the DOMdocument objects… delete the comment Dustin… this’ll only create confusion… :s

  18. Elegantly Fast Ajax at TrickJarrett.com

    [...] Elegantly Fast Ajax - I dig it. Share: These icons link to social bookmarking sites where readers can share and discover new web pages. [...]

  19. eglencen

    […] Making Ajax Elegantly Fast (tags: ajax javascript fast web development webdev) This entry was written by delicious and posted on 1 Ottobre 2007 at 01:25 and filed under Del.icio.us. Bookmark the permalink. Follow any comments here with the RSS feed for this post. Post a comment or leave a trackback: Trackback URL. […]

  20. myblg

    First, not that it’s a big deal, but what’s the point of the ‘return http;’ line in your original getXHR function? Since you are redefining the function anyway, it doesn’t seem like it would serve a purpose. However, since that wasn’t in your original writeup, I’m assuming you must have added it for a reason.

    Second, why don’t you test for XMLHttpRequest using a !! flag instead of a try / catch block? While the ‘catch’ block for the way you’re doing it would only get hit once in IE6, that’s once more than it needs to be hit it seems. The performance gain would probably be just a few milliseconds or so, and it’s maybe not even worth discussing, but I figured I’d ask anyway.

  21. stimulant » Blog Archive » things to look at (October 1st)

    [...] Making Ajax Elegantly Fast - [...]

  22. links for 2007-10-02 « Gevã Schaefer

    [...] Making Ajax Elegantly Fast Uma função para requisições Ajax rápida e elegante (tags: development javascript web ajax) [...]

  23. Making Ajax Elegantly Fast

    [...] Making Ajax Elegantly Fast [...]

  24. Mats Lindblad

    I’m not gonna pretend like I know anything but after reading “Peter’s Blog” I got the impression that you should use return getXHR(); and not http.
    Or did I miss anything?

  25. links for 2007-10-02 « toonz

    [...] Making Ajax Elegantly Fast (tags: ajax javascript webdev) [...]

  26. koekoeh

    very great info ^^! thanks for sharing Dustin.
    Much appreciated!

  27. : Ajax : Tecnica de uso de ajax elegante y eficaz | Ajax, Javascript, Xul, Internet, Recursos, Informatica … | Ajaxman

    [...] Visto en | Optimized Speedy Ajax Code y en Making Ajax Elegantly Fast [...]

  28. Douglas

    @Mats Lindblad:

    Yes, you are righ. The http variable is redundant, you could remove the “http = new XMLHttpRequest;” and “http = new ActiveXObject(msxml[i]);” lines and simply do “return getXHR();”.

    Also note that the code on this page is only good for testing on a local machine where there is no latency in the request.


    http.open(method, uri, true);
    handleReadyState(http, callback);

    This only processes handleReadyState once, so if readystate == 1 the first time (ie, the request has not been completed between the first and second lines of code in the snippet above), then the callback will never be called.

    There is also no mechanism to handle a failed request.

  29. Javascript News » Blog Archive » Optimized Speedy Ajax Code

    [...] Dustin Diaz has revisited his seven JavaScript techniques and has updated his XHR-getting-function to be faster, using the Lazy Function Definition Pattern, which ends up looking like: PLAIN TEXT JAVASCRIPT: [...]

  30. Lazy Function Definition Pattern | Matt Snider JavaScript Resource

    [...] Faster Ajax posted by admin at 8:37 pm [...]

  31. henrah

    here is another way you could define getXHR:


    var params = [
    ['XMLHttpRequest', null],
    ['ActiveXObject', 'MSXML2.XMLHTTP.3.0'],
    ['ActiveXObject', 'MSXML2.XMLHTTP'],
    ['ActiveXObject', 'Microsoft.XMLHTTP']
    ];

    function getXHR(){
    do try {
    return new window[params[0][0]](params[0][1]);
    } catch(ex) {
    params.unshift();
    } while (true);
    };

    When it is first called, the function will unshift through params until it finds a working constructor/argument pair. This becomes the default value.

  32. henrah

    Actually, I would amend the previous comment:


    var params = [
    ['XMLHttpRequest', null],
    ['ActiveXObject', 'MSXML2.XMLHTTP.3.0'],
    ['ActiveXObject', 'MSXML2.XMLHTTP'],
    ['ActiveXObject', 'Microsoft.XMLHTTP']
    ];

    function getXHR(){
    do try {
    return new window[params[0][0]](params[0][1]);
    } catch(ex) {
    if (! params.unshift()) throw new Error('XMLHttpRequests are not supported by this browser.');
    } while (true);
    };
  33. Ashutosh Nilkanth’s Blog » Making Ajax Elegantly Fast

    [...] Dustin Diaz shares some cool tips on making Async JavaScript (Ajax) work faster on the client-side using the Lazy Function Definition Design Pattern. Entry Filed under: Technology, .NET Web Dev, Linklog [...]

  34. John Resig

    “Dustin: Yes, there is… that piece of logic is run upon every request. And not only that, it doesn’t take advantage of IE’s newer methods for XMLHTTP such as MSXML2 and XMLHTTP.3.0.”

    You’re kidding, right? A boolean check for the existence of a property is worse than using closured function to wrap object creation? Bwah?

    As far as the XMLHTTP vs. other modules goes, I’ve discussed it with the IE team and they agreed that referencing a single version of the XMLHttpRequest code would reduce the number of inter-browser-version bugs and inconsistencies, especially considering that the newer versions don’t provide any tangible benefits in performing the requests. Thus, the code is significantly simplified.

  35. Alex

    Good read, I’ve been trying to learn a little more js on my own lately until my computer fried. Hopefully I’m gonna get back into it soon and I can work on my AJAX a little bit too. Thanks for the post

  36. Theodor Zoulias

    Making AJAX fast this way, slows page loading (a tiny bit). In terms of performance smaller scripts usually beat cleverer ones, due to time-to-download factor.

  37. H5N1

    I faced this problem almost one year ago and I solved in a similar way: implementing a new Object and using an Array to collect all the Objects. Then each use of the callback could be pointed to the relative Object in the Array.
    Obviously my code was not so elegant! :D

  38. Optimized Speedy Ajax Code

    [...] Dustin Diaz has revisited his seven JavaScript techniques and has updated his XHR-getting-function to be faster, using the Lazy Function Definition Pattern, which ends up looking like: [...]

  39. hax

    Sorry, your post is another bad example. Besides many issues some comments here pointed before, Lazy Func Def is totally a ANTI-PATTERN. I’ve left some comments on Peter’s article, you should read them.

    You only use getXHR() internally, so it will not as bad as Peter’s and FredCK’s examples. But it still no sence, because you can write the code more clearly and memory leak free (for example, henrah’s version, though it still can be imporved).

    Finally, about performance, in fact, all versions here are just same in practice (if we don’t consider the bugs in some of them), because compare to network IO, the cost of object initilization and exceptions can be ignored at all.

  40. David R

    This doesn’t seem to work as described.
    It fetches the file but never fires the callback func.
    You only seem to check the readystate once.

    To make it work I changed:
    handleReadyState(http, callback);
    to:
    http.onreadystatechange = function(){handleReadyState(http, callback)};

    Or maybe I’m missing something?

  41. alex

    Hi , i know that im going to sound really special for asking this, but how do i actually use your solutions function?


    asyncRequest('GET', 'foo.php', function(o) {
    console.log(o.responseText);
    });

    what variable holds the returned data?

    thank you in advance!

  42. Mark

    I have rewritten the code a bit to work with our site:


    var oAjaxInterface = {
    mInit : function ( method, uri, callback, postData, formular ) {
    var http = oAjaxInterface.mGetXHR();
    http.open( method, uri, true );
    if(formular)
    http.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
    oAjaxInterface.mHandleReadyState( http, callback );
    http.send( postData || null );
    return http;
    },
    mHandleReadyState : function ( http, callback ) {
    http.onreadystatechange = function(){
    if ( http && http.readyState == 4 && http.status == 200 ) {
    if ( callback ) {
    callback( http );
    }
    }
    };
    },
    mGetXHR : function () {
    var http;
    try {
    http = new XMLHttpRequest;
    oAjaxInterface.mGetXHR = function() {
    return new XMLHttpRequest;
    }
    } catch(e) {
    var msxml = [
    "MSXML2.XMLHTTP.5.0",
    "MSXML2.XMLHTTP.4.0",
    "MSXML2.XMLHTTP.3.0",
    "MSXML2.XMLHTTP",
    "Microsoft.XMLHTTP"
    ];
    for ( var i=0, len = msxml.length; i < len; i ) {
    try {
    http = new ActiveXObject( msxml[i] );
    oAjaxInterface.mGetXHR = function() {
    return new ActiveXObject( msxml[i] );
    }
    break;
    } catch(e) {}
    }
    }
    return http;
    }
    }

    And then call it like this:



    oAjaxInterface.mInit( "GET", "Some URL",
    function( response ) {
    oSendReq.mHandleResQ( response );
    }
    );

    I’m not sure if this will work as you use a return function, and i stripped that out.
    Do you see any problems with this?

  43. uglychart.com » Blog Archive » links for 2007-10-30

    [...] Making Ajax Elegantly Fast (tags: web2.0 programming ajax) [...]

  44. 2007 Hand-Picked Web Design Tutorials

    [...] Making Ajax Elegantly Fast - This article uses the Lazy Function Definition Pattern to reduce the time need to initialize an [...]

  45. Better ways to writing JavaScript

    [...] to calling these requests. The first thing you’ll need is an asyncRequest function. As well, I wrote one of these just a bit ago. It works like [...]

  46. Tom

    Dear Dustin,

    isn’t o.onreadystatechange = function() {} necessary before if (o && o.readyState == 4 && o.status == 200) for callback to work as intended?

    Tom

  47. tre

    hi dustin - where are you? there have been some fairly significant issues raised here, i’m curious if you have any response.

    what the hell…!? this whole “blog” idea, i just don’t know if it is any good. cancel everything!

  48. Pablo

    Hi
    How can I send post data using this function?
    I tried but it doesn’t work.
    I make it work adding:
    http.setRequestHeader(”Content-type”, “application/x-www-form-urlencoded”);
    http.setRequestHeader(”Content-length”, postData.length);
    http.setRequestHeader(”Connection”, “close”);
    before the http.send();

    is it ok?

    Thanks!

  49. Tom

    @Pablo

    You would write:


    asyncRequest('POST', 'foo.php', callbackFunction, postData);

    where callbackFunction is function you wish to call to deal with response text and postData are form field values you wish to send to foo.php (postData looks like: field1=value1&field2=value2…)

  50. Optymalizacja Ajaksa | CSS Mania

    [...] Diaz podjął temat optymalizacji połączeń przez XMLHttpRequest w artykule pt. Making Ajax Elegantly Fast. (Brak głosów) Loading [...]

  51. Devin

    I put it in myself and it works! :)


    http.onreadystatechange = function () { handleReadyState(http, callback); };

No comments:

Post a Comment