Making Ajax Elegantly Fast
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);
});
September 30th, 2007 at 3:33 am
Very interesting! Thanks for sharing that!
September 30th, 2007 at 6:24 am
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.
September 30th, 2007 at 7:13 am
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.
Any problem with this?
September 30th, 2007 at 8:30 am
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 :)
September 30th, 2007 at 12:34 pm
@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 :
September 30th, 2007 at 2:18 pm
yes, thats true
changing workplaces often changes the thinking to
ps: nice to read something new on your blog :)
September 30th, 2007 at 4:26 pm
[...] 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. [...]
September 30th, 2007 at 11:55 pm
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
instead of this
? 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.
October 1st, 2007 at 1:00 am
@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…
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…
October 1st, 2007 at 3:19 am
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:
it is:
3. I do not like your lazy-like getXHR function implementation, below is my implementation:
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.
October 1st, 2007 at 3:24 am
And this is my getXHR function:
Sorry for posting again, the HTML entities………….
October 1st, 2007 at 4:29 am
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.
October 1st, 2007 at 8:12 am
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).
October 1st, 2007 at 9:38 am
@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.
October 1st, 2007 at 10:37 am
[...] http://www.dustindiaz.com/faster-ajax/ [...]
October 1st, 2007 at 1:06 pm
@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
October 1st, 2007 at 1:08 pm
crap… those are the DOMdocument objects… delete the comment Dustin… this’ll only create confusion… :s
October 1st, 2007 at 1:54 pm
[...] Elegantly Fast Ajax - I dig it. Share: These icons link to social bookmarking sites where readers can share and discover new web pages. [...]
October 1st, 2007 at 3:29 pm
[…] 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. […]
October 1st, 2007 at 3:30 pm
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.
October 1st, 2007 at 5:51 pm
[...] Making Ajax Elegantly Fast - [...]
October 1st, 2007 at 8:24 pm
[...] Making Ajax Elegantly Fast Uma função para requisições Ajax rápida e elegante (tags: development javascript web ajax) [...]
October 2nd, 2007 at 1:12 am
[...] Making Ajax Elegantly Fast [...]
October 2nd, 2007 at 3:30 am
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 nothttp.Or did I miss anything?
October 2nd, 2007 at 4:25 pm
[...] Making Ajax Elegantly Fast (tags: ajax javascript webdev) [...]
October 3rd, 2007 at 1:34 am
very great info ^^! thanks for sharing Dustin.
Much appreciated!
October 3rd, 2007 at 8:01 am
[...] Visto en | Optimized Speedy Ajax Code y en Making Ajax Elegantly Fast [...]
October 3rd, 2007 at 12:01 pm
@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.
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.
October 3rd, 2007 at 4:54 pm
[...] 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: [...]
October 3rd, 2007 at 8:37 pm
[...] Faster Ajax posted by admin at 8:37 pm [...]
October 3rd, 2007 at 11:44 pm
here is another way you could define
getXHR:When it is first called, the function will unshift through
paramsuntil it finds a working constructor/argument pair. This becomes the default value.October 4th, 2007 at 12:02 am
Actually, I would amend the previous comment:
October 4th, 2007 at 4:32 am
[...] 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 [...]
October 4th, 2007 at 2:39 pm
“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.
October 5th, 2007 at 11:28 am
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
October 5th, 2007 at 12:03 pm
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.
October 8th, 2007 at 9:36 am
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
October 9th, 2007 at 3:05 am
[...] 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: [...]
October 13th, 2007 at 12:26 pm
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.
October 17th, 2007 at 3:41 pm
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?
October 21st, 2007 at 8:34 pm
Hi , i know that im going to sound really special for asking this, but how do i actually use your solutions function?
what variable holds the returned data?
thank you in advance!
October 22nd, 2007 at 2:54 am
I have rewritten the code a bit to work with our site:
And then call it like this:
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?
October 29th, 2007 at 9:31 pm
[...] Making Ajax Elegantly Fast (tags: web2.0 programming ajax) [...]
November 29th, 2007 at 3:38 pm
[...] Making Ajax Elegantly Fast - This article uses the Lazy Function Definition Pattern to reduce the time need to initialize an [...]
December 1st, 2007 at 6:29 pm
[...] 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 [...]
December 3rd, 2007 at 4:37 am
Dear Dustin,
isn’t
o.onreadystatechange = function() {}necessary beforeif (o && o.readyState == 4 && o.status == 200)for callback to work as intended?Tom
December 5th, 2007 at 7:13 pm
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!
December 11th, 2007 at 2:18 pm
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!
December 13th, 2007 at 12:33 pm
@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…)
December 19th, 2007 at 2:42 am
[...] Diaz podjął temat optymalizacji połączeń przez XMLHttpRequest w artykule pt. Making Ajax Elegantly Fast. (Brak głosów) Loading [...]
December 30th, 2007 at 2:58 pm
I put it in myself and it works! :)
http.onreadystatechange = function () { handleReadyState(http, callback); };