Thinking Async

Avatar of Chris Coyier
Chris Coyier on

Get affordable and hassle-free WordPress hosting plans with Cloudways, now offering 40% off for 4 months, and 40 free migrations.

Here’s the rub: when you load JavaScript from a third party you should do it asynchronously. You might want to load your own scripts asynchronously too, but for this article let’s focus on third parties.

There are two reasons for this:

  1. If the third-party goes down or is slow, your page won’t be held up trying to load that resource.
  2. It can speed up page loads.

At Wufoo, we just switched over to an asynchronous embed snippet. Users who build forms with Wufoo and want to embed them on their site are now recommended to use it. We did it for exactly those reasons above. It’s the responsible thing to do for a web service that asks people to link to resources on that services site.

Let’s explore this whole async thing.

Uhm. What?

There is a little terminology involved here that will help us understand the umbrella “asynchronous” term.

“Parser blocking” – The browser reads your HTML and when it comes to a <script> it downloads that entire resource before moving on with the parsing. This definitely slows down page loads, especially if the script is in the head or above any other visual elements. This is true in older browsers as well as modern browsers if you don’t use the async attribute (more on that later). From the MDN docs: “In older browsers that don’t support the async attribute, parser-inserted scripts block the parser…”

To prevent problematic parser blocking, scripts can be “script inserted” (i.e. insert another script with JavaScript) which then forces them to execute asynchronously (except in Opera or pre 4.0 Firefox).

“Resource blocking” – While a script is being downloaded, it can prevent other resources from downloading at the same time as it. IE 6 and 7 do this, only allowing one script to be downloaded at a time and nothing else. IE 8 and Safari 4 allow multiple scripts to download in parallel, but block any other resources (reference).

Ideally we fight against both of these problems and speed up page loading (both actual and perceived) speed.

The HTML5 Way

There is an async attribute for the script tag in HTML5 (spec). Example:

<script async src="https://third-party.com/resource.js"></script>

The browser support for it is Firefox 3.6+, IE 10+, Chrome 2+, Safari 5+, iOS 5+, Android 3+. No Opera support yet.

If you are going to load a script directly like that, using this attribute is probably a good idea. It prevents parser blocking. These newer browsers don’t have any big problems with resource blocking, but the parser thing is a big deal. We aren’t using this at Wufoo because we need way deeper browser support than that.

The Classic Async Way

Here is the basic async script loading pattern that will get you as deep of browser support as you’d ever need:

<script>
  var resource = document.createElement('script'); 
  resource.src = "https://third-party.com/resource.js";
  var script = document.getElementsByTagName('script')[0];
  script.parentNode.insertBefore(resource, script);
</script>

Here’s a more efficient version with a wrapper around those variables (credit Mathias Bynens):

(function(d, t) {
    var g = d.createElement(t),
        s = d.getElementsByTagName(t)[0];
    g.src = 'https://third-party.com/resource.js';
    s.parentNode.insertBefore(g, s);
}(document, 'script'));
Update: Note that this is no longer recommended! The era that made this a useful pattern is over, and you should mostly just use the async attribute.

Ad Networks

BuySellAds is an advertising network that was one of the first to deliver ads asynchronously. This is their pattern:

<script type="text/javascript">
(function(){
  var bsa = document.createElement('script');
     bsa.type = 'text/javascript';
     bsa.async = true;
     bsa.src = 'https://s3.buysellads.com/ac/bsa.js';
  (document.getElementsByTagName('head')[0]||document.getElementsByTagName('body')[0]).appendChild(bsa);
})();
</script>

Couple of things to note here:

  • They are setting the async attribute of the script to true before appending it. This is useful exclusively for Firefox 3.6 which is the only browser that doesn’t do that by default. This can probably be omitted in most scenarios. Setting the script type definitely isn’t needed.
  • As in the simple pattern above, the src is set using a protocol-relative URL. This is a darn useful way to load the script from either HTTP or HTTPS depending on the page that requested it. We absolutely need to do this at Wufoo, but unfortunately found that it throws an error in IE 6 with the default security settings. If you don’t need IE 6 support, by all means use it.
  • They are appending the script to the head or the body, whichever is found first. Totally fine way to do it, but just as safe to look for a script element since the code itself is a script element.

The Deck uses a different pattern:

<script type="text/javascript">
//<![CDATA[
(function(id) {
 document.write('<script type="text/javascript" src="' +
   'http://connect.decknetwork.net/deck' + id + '_js.php?' +
   (new Date().getTime()) + '"></' + 'script>');
})("DF");
//]]>
</script>

I’m pretty sure this still qualifies as asynchronous because the resource they are loading is a script-injected script, meaning it won’t parser block.

What if you need a callback?

Sometimes you need to load a third-party script, then once that script is loaded, fire off some custom code. That custom code probably calls some function defined in the third-party script with data that is unique to a specific page.

<script src="https://third-party.com/resource.js"></script>
<script>
  doSomethingFancy('chriscoyier');
</script>

Typekit is in this situation. You need to load the Typekit JavaScript, then kick it off. Typekit is actually taking advantage of the fact that scripts block the parser. If your page is held up until their script is loaded, you won’t see the “FOUT” (Flash Of Unstyled Text), usually only problematic in Firefox, but also problematic in Typekit where @font-face resources are being loaded via JavaScript.

<script type="text/javascript" src="https://use.typekit.com/abc1def.js"></script>
<script type="text/javascript">try{Typekit.load();}catch(e){}</script>

This is clever, but it’s slightly dangerous. If Typekit were to be down or slow: “What was once a desirable delay in rendering to hide the FOUT becomes a serious problem when the script takes longer than a few seconds to load.” (reference).

Here’s a way to load Typekit async style:

<script type="text/javascript">
  TypekitConfig = {
    kitId: 'abc1def'
  };
  (function() {
    var tk = document.createElement('script');
    tk.src = 'https://use.typekit.com/' + TypekitConfig.kitId + '.js';
    tk.type = 'text/javascript';
    tk.async = 'true';
    tk.onload = tk.onreadystatechange = function() {
      var rs = this.readyState;
      if (rs && rs != 'complete' && rs != 'loaded') return;
      try { Typekit.load(TypekitConfig); } catch (e) {}
    };
    var s = document.getElementsByTagName('script')[0];
    s.parentNode.insertBefore(tk, s);
  })();
</script>

There is an awful lot of gnarly code there to handle the onload callback. That’s just the way it is to get callbacks working with deep browser support, unfortunately. Be forewarned, using this pattern actually brings back the problem of FOUT. If you want to go async with Typekit and have just as good an experience as you do normally, read their post on it which covers some clever class name manipulation and font events.

jQuery and other Script Loaders

If you are already using jQuery, loading up a third-party script and getting a callback when it’s ready is pretty easy:

$.ajax({
  url: 'https://third-party.com/resource.js',
  dataType: 'script',
  cache: true, // otherwise will get fresh copy every page load
  success: function() {
    // script loaded, do stuff!
  }
}

I’m sure other libraries have similar abilities. It’s the classic thing JavaScript libraries are good at helping with. Also see getScript which might be a bit more succinct.

If you aren’t using a library and are concerned about file size, YepNope is a super tiny script loader that can help as well. It’s ideal use is performing a test to see if you need to load the script or not, but it has direct methods as well:

yepnope.injectJs("https://third-party.com/resource.js", function () {
  // script loaded, do stuff!
});

Relevant: Max Wheeler’s article Loading Typekit Asynchronously with YepNope.

Do you really need a callback?

At Wufoo, we figured yes, we need a callback. We need to load the form-embedding JavaScript resource, then call a function with all the specifics of the user’s form. This is how we used to do it:

<script type="text/javascript">var host = (("https:" == document.location.protocol) ? "https://secure." : "http://");document.write(unescape("%3Cscript src='" + host + "wufoo.com/scripts/embed/form.js' type='text/javascript'%3E%3C/script%3E"));</script><br />
<script type="text/javascript">
var z7p9p1 = new WufooForm();
z7p9p1.initialize({
'userName':'chriscoyier', 
'formHash':'z7p9p1', 
'autoResize':true,
'height':'546', 
'ssl':true});
z7p9p1.display();
</script>

That set of key/value pairs is useful for a user to be able to see, change, and add things to. We toyed around with some ways we could keep that, but pass that data as part of the URL when calling the script. The script on our end would really be PHP and be able to $_GET the values. That would avoid needing to deal with all the ugly callback code in an async pattern. Possibly something like:

script.src = 'https://wufoo.com/form.js?data=' + JSON.stringify(options);

But ultimately, we decided against it. The callback code isn’t that bad, stringifying JSON doesn’t have very deep browser support (and it’s impractical to include a polyfill in copy and paste code), and having our form.js well-cached ideal.

Social Media

Social media buttons are a classic case of needing third-party JavaScript on your page. Interestingly, the three biggest players provide their code in an async pattern already.

Facebook

<div id="fb-root"></div>
<script>(function(d, s, id) {
  var js, fjs = d.getElementsByTagName(s)[0];
  if (d.getElementById(id)) return;
  js = d.createElement(s); js.id = id;
  js.src = "https://connect.facebook.net/en_US/all.js#xfbml=1&appId=200103733347528";
  fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));</script>

Twitter

<a href="https://twitter.com/share" class="twitter-share-button">Tweet</a>
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="https://platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script>

Google Plus

<g:plusone annotation="inline"></g:plusone>
<script type="text/javascript">
  (function() {
    var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;
    po.src = 'https://apis.google.com/js/plusone.js';
    var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
  })();
</script>

Cleaning House

All the above are very similar yet slightly different. Plopping them all down as-is on a page can make code purists like us cry. Nicholas Gallagher has a really clean efficient way to put them all together:

(function(doc, script) {
    var js, 
        fjs = doc.getElementsByTagName(script)[0],
        add = function(url, id) {
            if (doc.getElementById(id)) {return;}
            js = doc.createElement(script);
            js.src = url;
            id && (js.id = id);
            fjs.parentNode.insertBefore(js, fjs);
        };

    // Google Analytics
    add('https:://ssl.google-analytics.com/ga.js', 'ga');
    // Google+ button
    add('https://apis.google.com/js/plusone.js');
    // Facebook SDK
    add('https://connect.facebook.net/en_US/all.js', 'facebook-jssdk');
    // Twitter SDK
    add('https://platform.twitter.com/widgets.js', 'twitter-wjs');
}(document, 'script'));

Dealing with CMSs

WordPress is friggin huge. So are all the other major CMS’s. They can’t be ignored when you’re a third-party offering up copy-paste JavaScript code. The key, of course, is testing. The single most important thing is not including double line-breaks inside the code. Like:

<script type="text/javascript">
var s = d.createElement(t), options = {

   foo: bar

}
// The line break space above is bad!
</script>

That might look nice and clean, but the “autop” behavior of WordPress will insert paragraph tags around various parts of that, which of course prevent the script from executing as expected.

Final Wufoo Snippet

This is what we ended up with:

<div id="wufoo-z7w3m7">
Fill out my <a href="http://examples.wufoo.com/forms/z7w3m7">online form</a>.
</div>
<script type="text/javascript">var z7w3m7;(function(d, t) {
var s = d.createElement(t), options = {
'userName':'examples', 
'formHash':'z7w3m7', 
'autoResize':true,
'height':'260',
'async':true,
'header':'show', 
'ssl':true};
s.src = 'https://wufoo.com/scripts/embed/form.js';
s.onload = s.onreadystatechange = function() {
var rs = this.readyState; if (rs) if (rs != 'complete') if (rs != 'loaded') return;
try { z7w3m7 = new WufooForm();z7w3m7.initialize(options);z7w3m7.display(); } catch (e) {}};
var scr = d.getElementsByTagName(t)[0], par = scr.parentNode; par.insertBefore(s, scr);
})(document, 'script');</script>

Honestly, the “size” of the snippet was a concern. Fifty lines is just too much for something like this. It’s at 19 now, which is more than we had, but is acceptable. Many of those lines are the options object, which we could squish up into fewer lines but it’s nicer having them each on separate lines for read/changability.

We need to be supporting IE 6 still, so unfortunately no protocol-relative URL’s for us. We’re using the location.protocol test.

It’s a bit bigger than your “average” async snippet (if there is such a thing as an average snippet), but that’s OK. It’s got quite a bit of work to do and it does it well.

We talk about a number of the advantages to it in the announcement blog post. My favorite is that you can now move the script to anywhere you’d like, it doesn’t need to be exactly where you want the form to appear like it used to be.

Waterfalls

If you are interested in doing some testing on resource loading, looking at resource waterfalls is particularly useful. Modern web dev tools have this built in, like the “Network” tab in web inspector or “Net” tab of Firebug. But the old school dev tools in IE 6-8 don’t offer that information. Fortunately the website Web Page Test does (it’s kinda ugly but it’s very cool).

While I was doing some testing for the Wufoo snippet in IE 6, I could prove our new way was non-blocking and the old was way blocking:

Alright that’s all I got. I feel a little weird writing about all this stuff because it’s all fairly new to me and I feel like I’m far from an expert. So feel free to correct me on anything or share your own async experiences.