Loading JavaScript without blocking

I was reading Steve Souder’s blog post on loading scripts without blocking in which he notes that dynamically creating a <script> element and assigning its src attribute leads to a download that doesn’t block other downloads or page processes. His post is missing an example of how to do this, so I thought I’d pick up from there. I think most developers tend to use JavaScript libraries for such behavior (the YUI Get utility comes to mind) but a discussion of the underlying technique is still useful to know.

The basic approach to downloading JavaScript without blocking is quite straightforward:

var script = document.createElement(“script”);
script.type = “text/javascript”;
script.src = “file.js”;
document.body.appendChild(script);

This is about as easy as it gets, you just create a new DOM element, assign its properties and add it to the page. There are two things to note about this code. First, the download doesn’t actually begin until the script node is added to the document. This is different from dynamically creating an <img> element, for which assigning the src automatically begins the download even before the node is added to the document. The second thing to note is that you can add the script node either to the <head> or <body>; it really doesn’t matter. That’s all it takes to dynamically load a JavaScript file without blocking the page.

Of course, you may also want to be notified when the JavaScript file is fully downloaded and executed, and that’s where things get a bit tricky. Most modern browsers (Firefox, Safari, Opera, Chrome) support a load event on <script> elements. This is an easy way to determine if the script is loaded:

//Firefox, Safari, Chrome, and Opera
var script = document.createElement(“script”);
script.type = “text/javascript”;
script.src = “file.js”;
script.onload = function(){
alert(“Script is ready!”);
};
document.body.appendChild(script);

The real problem is in Internet Explorer, which uses the readyState property to indicate the state of the script and a readystatechange event to indicate when that property has changed. In this case, readyState isn’t a number as it is with the XMLHttpRequest object; instead, it’s one of five possible values:


  • “uninitialized” – the default state.

  • “loading” – download has begun.

  • “loaded” – download has completed.

  • “interactive” – data is completely available but isn’t fully available.

  • “complete” – all data is ready to be used.


Even though the MSDN documentation indicates that these are the available values forreadyState, in reality, you’ll never see all of them. The documentation also applies to other elements that also support readyState and leaves us hanging with a rather cryptic description of which readyState values to expect:

An object’s state is initially set to uninitialized, and then toloading. When data loading is complete, the state of the link object passes through the loaded and interactive states to reach the complete state.
The states through which an object passes are determined by that object; an object can skip certain states (for example,interactive) if the state does not apply to that object.

Even stranger is that the final readyState isn’t always complete. Sometimes, readyStatestops at loaded without going on to complete and sometimes it skips over loaded altogether. The best approach is to check for both readyState values and remove the event handler in both cases to ensure you don’t handle the loading twice:
//Internet Explorer only
var script = document.createElement(“script”);
script.type = “text/javascript”;
script.src = “file.js”;
script.onreadystatechange = function(){
if (script.readyState == “loaded” ||
script.readyState == “complete”){
script.onreadystatechange = null;
alert(“Script is ready!”);
}
};
document.body.appendChild(script);

You can wrap these two approaches pretty easily to create a cross-browser function to dynamically load JavaScript:
function loadScript(url, callback){

var script = document.createElement(“script”)
script.type = “text/javascript”;

if (script.readyState){ //IE
script.onreadystatechange = function(){
if (script.readyState == “loaded” ||
script.readyState == “complete”){
script.onreadystatechange = null;
callback();
}
};
} else { //Others
script.onload = function(){
callback();
};
}

script.src = url;
document.body.appendChild(script);
}

To use this, just pass in the URL to retrieve and a function to call once it’s loaded:
loadScript(“http://yui.yahooapis.com/2.7.0/build/yahoo/yahoo-min.js“,
function(){
YAHOO.namespace(“mystuff”);

//more…
});

Loading scripts in this way prevents them from blocking the download of other resources on the page or preventing the display from rendering. It’s a really useful technique when performance is important (and let’s face it, when is it never?). The really cool thing is that YUI 3 is built completely around the idea of non-blocking JavaScript downloads. All you need to do is download the ~20KB seed file and then specify the additional resources you want to load, such as:
YUI().use(“dom”, function(Y){
Y.DOM.addClass(document.body, “active”);
});

Behind the scenes, YUI constructs the appropriate URL for the dom module and downloads it, automatically executing the callback function when the code is ready. This can really improve the initial download time of an overall page by asynchronously downloading the rest of the JavaScript code.

Loading JavaScript without blocking is a really important technique to understand and use in web applications that are concerned with page load performance. JavaScript blocking slows down the entire user experience, but it no longer has to.

Disclaimer: Any viewpoints and opinions expressed in this article are those of Nicholas C. Zakas and do not, in any way, reflect those of my employer, my colleagues, Wrox Publishing, O’Reilly Publishing, or anyone else. I speak only for myself, not for them.

Both comments and pings are currently closed.




Refer Original Article

A usefull comment follows:
This is a great post. Not having been in the Web Development field for very long, it came as quite a shock to find out the embedded script tags were blocking content download. In general, the most difficult aspects are managing scripts that have all ready been loaded and detection of the onload event. My attempts at implementing a script manager lead me to the following cross-browser issues that need to be managed.

Firefox guarantees the scripts will be executed in the order in which they were attached. If you insert scripts A and B in that order they will download asynchronously; however, script A must FINISH before script B can execute. So, if script A is lagged and script B finished quickly, it will not execute (nor will its onload event fire) until A has completed. This is in contrast to what I have seen in Safari, IE and Chrome.

In WEbKit, if the script’s src is in cache the onload event will fire as soon as the src is set. That means that, in your example, if file.js is in the browser cache, the onload event will never fire. I have found that this requires you to do two things: Setting the src attribute only AFTER the script is inserted into the DOM and subscribing to the onload event prior to DOM insertion. (This is definitely better than old versions of Safari which did not support the onload event on script elements). IMHO, IE’s readystate is more robust than a single onload event. Having this attribute available in Safari would certainly solve the problem.

YUI’s get() method only supports synchronized download of scripts. So, telling YUI to get Script A and Script B will download and execute Script A and only start on Script B when it receives A’s onload event. This definitely makes loading external JS files that need another library while they initialize, but it does not lead to an decrease in download times. A feature that seems to be unique to IE is that it will begin downloading an element as soon as the SRC attribute is set. That means that creating scriptEl and then doing scriptEl.src=”file.js” will begin the download of file.js. The script will not be executed, however, until you attach it to the document itself. This is similar to how all browsers handle the src attribute on an image and I think makes sense.

You can use the fact that FF guarantees execution order and that IE downloads as soon as the src attribute is set to achieve asynchronous downloads but synchronized execution. If Script B needs functions defined in Script A while it executes (not within functions defined in Script B) then B cannot run until A is complete. So, in FF you can simply create and attach script A followed by Script B. In IE, however, you create Script A and Script B at the same time. Then attach Script B after script A’s readystate is complete. Script B will change readystate almost immediately since it has all ready been fetched (In fact, Script B’s onreadystate function will fire before control is returned to the attaching function so watch out)

A couple of other idiosyncrasies:

1) When setting innerHTML to a value that contains a scirpt element, it is well known IE will not execute that script (FF will); however, what’s not as well known is that IE will still DOWNLOAD any scirpt with a src attribute. If you use innerHTML to extract script tags in an AJAX routine or something, watch out for this especially if your remote scripts are uncacheable (we have the problem with AJAX content that contains ad banner scripts since the server sets explicit no-cache headers).

2) If you’re using a timeout handler of some kind to detect when a remote server is unavailable, you can remove the script element which will cancel the download, preventing script execution; however, FireFox does not support this. Couple this with FF’s need to execute scripts in insertion order and you have no way to handle a long-loading script from hanging the rest of the script elements (and thereby preventing your page from being functional)

ServerHerder on September 28th, 2009 at 3:00 pm