A Simple Async JavaScript Loader

When a JavaScript file is loaded into the page it can block the rendering of the elements that come after it in the page. For example, if you add an external JavaScript to display modals in the header of a page the elements following it won't start to progressively render what comes after the script until after that file is downloaded.

This can cause a few problems for users.

  1. If a user is in a country or location that blocks the script the page isn't going to load until the script times out. This can happen quite easily. For example, Twitter is blocked in China. If you have a Twitter widget in your page than the content after the twitter script won't render until the browser times out trying to fetch the script. For many browsers this is around 20 seconds.
  2. Users expect pages to load fast. Three seconds or less fast. Even on mobile. When you include a script to a 3rd party your page rendering happens at the speed of their network. If they experience a slow down so does your site in rendering content. Do you want ad network or social media widget slow downs to slow down rendering of your content?
  3. From time to time stuff on the Internet goes down. It's happened to Twitter, Facebook, and lots of sites. We would like them to be fault tolerant so this doesn't happen but in practice it does. With regular script loading and browser timeouts one of these sites going down can cause your content to have huge delays in loading.
  4. We often load more than one thing in a page. Say you have a script for Facebook and for Twitter loading in your page. If they are blocking than the first one does it's blocking business and then the second one does the same. These can happen serially which causes overall slow downs in pages even when everything is working perfectly.

The solution to these types of problems is to load the scripts Asynchronously (a.k.a using async). Let's look at how to do that with a short snippet.

Native Browser Support?

This seems like something that should have native browser support and it almost does. Per HTML5 the script tag has been given an async property that allows scripts to be executed asynchronously. The problem with just using this is that Internet Explorer prior to IE 10 doesn't support it. In time we will hopefully be able to use native browser support.

Simple Async Loading

Steve Souders shared a nice snippet in a recent post that shows how Google Analytics loads their code using async:

var sNew = document.createElement("script");
sNew.async = true;
sNew.src = "http://example.com/foo.js";
var s0 = document.getElementsByTagName('script')[0];
s0.parentNode.insertBefore(sNew, s0);

This snippet will add a script tag to the page and the script will be executed once it's downloaded. Elements in the page after this script can be rendered while the script is still downloading.

But, how can we improve on this? What if we wanted to turn this into a function so we can reuse it? What if we want to execute a callback after the script is completed downloading?

A Reusable Function And Optional Callback

asyncLoadScript = function (url, callback) {

  // Create a new script and setup the basics.
  var script = document.createElement("script"),
      firstScript = document.getElementsByTagName('script')[0];

  script.async = true;
  script.src = url;

  // Handle the case where an optional callback was passed in.
  if ( "function" === typeof(callback) ) {
    script.onload = function() {
      callback();

      // Clear it out to avoid getting called more than once or any memory leaks.
      script.onload = script.onreadystatechange = undefined;
    };
    script.onreadystatechange = function() {
      if ( "loaded" === script.readyState || "complete" === script.readyState ) {
        script.onload();
      }
    };
  }

  // Attach the script tag to the page (before the first script) so the magic can happen.
  firstScript.parentNode.insertBefore(script, firstScript);
};

If this is included in a page we can load a script simply by calling:

asyncLoadScript('http://example.com/foo.js');

And, if we want to execute a callback after the script is completed loading we can do something like:

asyncLoadScript('http://example.com/foo.js', function() {
  alert('The script is loaded!');
});

Notes And Warnings

When working with Asynchronous scripts be careful when one script is dependent on another. If you try to load them both using async the dependent script might load before the script it's dependent on and fail because it's dependency isn't available yet.

Async script loading is going to block the pages onload event. While elements will progressively render using async the onload event waits for everything to complete. If you want to have scripts that don't block the onload event Stoyan Stefanov recently posted a work around.