Non-onload-blocking async JS

June 28th, 2012. Tagged: JavaScript, performance

Update Oct 2013: for a more bulletproof version, tested in the wild, IE and all, check Philip's snippet at http://www.lognormal.com/blog/2012/12/12/the-script-loader-pattern/

Asynchronous JS is cool but it still blocks window.onload event (except in IE before 10). That's rarely a problem, because window.onload is increasingly less important, but still...

At my Velocity conference talk today Philip "Log Normal" Tellis asked if there was a way to load async JS without blocking onload. I said I don't know, which in retrospect was duh! because I spoke about Meebo's non-onload-blocking frames (without providing details) earlier in the talk.

Stage fright I guess.

Minutes later in a moment of clarity I figured Meebo's way should help. Unfortunately all Meebo docs are gone from their site, but we still have their Velocity talk from earlier years (PPT). There are missing pieces there but I was able to reconstruct a snippet that should load a JavaScript asynchronously without blocking onload.

Here it goes:

(function(url){
  var iframe = document.createElement('iframe');
  (iframe.frameElement || iframe).style.cssText = "width: 0; height: 0; border: 0";
  var where = document.getElementsByTagName('script');
  where = where[where.length - 1];
  where.parentNode.insertBefore(iframe, where);
  var doc = iframe.contentWindow.document;
  doc.open().write('<body onload="'+
    'var js = document.createElement(\'script\');'+
    'js.src = \''+ url +'\';'+
    'document.body.appendChild(js);">');
  doc.close();
})('http://www.jspatterns.com/files/meebo/asyncjs1.php');

The demo page is right here. It loads a script (asyncjs1.php) that is intentionally delayed for 5 seconds.

Features

  • loads a javascript file asynchronously
  • doesn't block window.onload nor DOMContentLoaded
  • works in Safari, Chrome, Firefox, IE6789 *
  • works even when the script is hosted on a different domain (third party, CDN, etc), so no x-domain issues.
  • no loading indicators, the page looks done and whenever the script arrives, it arrives and does its thing silently in the background. Good boy!

* The script works fine in Opera too, but blocks onload. Opera is weird here. Even regular async scripts block DOMContentLoaded which is a shame.

Drawback

The script (asyncjs1.php) runs is in an iframe, so all document and window references point to the iframe, not the host page.

There's an easy solution for that without changing the whole script. Just wrap it in an immediate function and pass the document object the script expects:

(function(document){
 
  document.getElementById('r')... // all fine
 
})(parent.document);

How does it work

  1. create an iframe without setting src to a new URL. This fires onload of the iframe immediately and the whole thing is completely out of the way
  2. style the iframe to make it invisible
  3. get the last script tag so far, which is the snippet itself. This is in order to glue the iframe to the snippet that includes it.
  4. insert the iframe into the page
  5. get a handle to the document object of the iframe
  6. write some HTML into that iframe document
  7. this HTML includes the desired script

Tell your friends about this post: Facebook, Twitter, Google+

32 Responses

  1. Nice, thanks for piecing this together, I bet Meebo has a lot of cool hacks that may be disappearing along with their products =(

    One question, since your talk was mainly about dealing with third party scripts. Since you might not control the target script, how do you adapt document to parent.document? I guess you could proxy and wrap the script via a PHP page you DO control, but is there a way to just load the third party script directly without messing up uses of window/document?

  2. This is exactly how YSlow bookmarklet works plus it sandboxes YUI into it: http://www.yuiblog.com/blog/2011/07/18/next-gen-yslow-powered-by-yui/

    Kudos to Caridy who was inspired by Meebo’s technique and helped me finding the best solution for injecting YSlow as a bookmarklet seamlessly.

  3. Sorry my typo above, the correct link to Caridy’s Twitter is http://twitter.com/caridy.
    * feature request: edit after posting comments

  4. I started typing up some what I thought were improvements to this and it started getting a bit long.

    So I’ve posted it on my own blog instead: http://blog.vervestudios.co/blog/post/2012/06/28/Non-onload-blocking-Async-JS-with-requirejs.aspx

  5. Thanks for re-surfacing Meebo’s loading technique again! It’s kind of amazing how cutting-edge that work was (they built it way back in 2010). I think their original code is here:

    https://github.com/meebo/embed-code

    …but I actually like the simplicity of your reconstructed snippet better. After a quick look though, there are a couple tweaks I noticed you might want to add:

    1. Avoid SSL warnings: iframe.src defaults to “about:blank” in IE6, which it then treats as insecure content on HTTPS pages. We found that initializing iframe.src to “javascript:false” fixes this up :)
    2. Avoid crossdomain exceptions: anonymous iframe access will throw exceptions if the host page changed the document.domain value in IE. The original Meebo code falls back to a “javascript:” URL when this happens.

    At Olark, we use the Meebo technique (plus some enhancements) for distributing our own embed code. The implementation has worked really well in the field:

    http://www.olark.com/spw/2011/10/lightningjs-safe-fast-and-asynchronous-third-party-javascript

    It would *awesome* to shrink down the size, while still handling those corner cases and ensuring forward-compatibility. I always find myself wishing for a better way :)

    @HB: I think proxying might be the only way to wrap existing scripts outside your control. This was a common question after our blog post too. I think it mostly comes down to 3rd parties adopting this pattern over time.

  6. Thanks @David and @Marcel!

    @HB, good question, hmm. Unfortunatelly this snippet requires changes to the script being loaded, even if it’s just a closure around the whole thing passing document, window, etc

  7. @Matt, thanks for the pointers! Ben Vinegar of Disqus pointed last night to your lightningjs, it’s really cool. Just a tad long, but hey, browsers are messy :)

    Agree, javascript:false is an easy fix, this is also what Facebook SDK does

    #2 issue is really annoying and adding more code for this edge case

    and here’s YSlow’s bookmarklet code (as Marcel mentioned above) in a fiddle: http://jsfiddle.net/J3uaK/ some clever tricks for js golfing there

  8. Hi, Stoyan. I’ve always assumed that an asynchronous script could be loaded and executed without blocking onLoad simply by letting onLoad trigger it. Would this not work?

  9. I have been looking for this technique since long time. Thanks for the post.

  10. @Warren, yes, anything you do like:

    window.onload = function(){/* stuffs */};

    is obviously not going to block `onload`

  11. Hi, again, Stoyan.

    If the simple one-liner you provided in your previous comment will solve the problem, why then would we use the 13-liner you propose in the article? It seems like extra work and extra processing time. I’m sure you’re seeing some benefit that I am missing. Would you please bring me up to speed? Thanks bunches.

  12. @Warren the one liner doesn’t solve the real problem, it just solves one problem and creates a new problem.

    To define the problem correctly, “How do you download scripts _in parallel_ with the rest of your content without blocking onload”. The _in parallel_ part is important. If you defer downloading of the script until after onload, then the total time to download assets increases while network throughput is reduced. The long script gets you the best of both worlds: parallelization + non-blocking.

    As to why this came up… Stoyan wrote this in response to my question at Velocity, and my script can absolutely not load after onload fires because it needs to measure the time when onload fires. It is acceptable for the script to finish loading after onload if the script is too slow, but in the majority case, we need to make sure the script completes loading before onload fires.

  13. @Warren, By moving the call to on load would increase the overall load time of the page. Whole idea of making non blocking is start the call early without affecting window on load.

  14. [...] 3 shows how they incorporated New Relic into the Web Fabric service.* Stoyan Stefanov shows how to load JavaScript asynchronously without blocking onload.No comments yetLeave a Reply Cancel replyYour email address will not be [...]

  15. May be someone has mentioned this already, but adding a setTimeout( ,10); in iframe seems to fix the problem for Opera browser. We are struggling to support Opera in our company =)
    var requestLink = ‘foo.bar.baz/script.js’;
    loader.open();
    loader.write(”);
    loader.close();

  16. Oops, something happend with include code snippet. A link to test page than:

    http://banners.adfox.ru/120808/adfox/215374/test3.Stoyan.html

  17. Hi

    I’m working in a little tiny loader for javascript sources using CORS and make the web more fast !

    The goal is load multiples js in parallel and execute in order without blocking DOMReady or onload.

    https://github.com/pablomoretti/jcors-loader

  18. Advanced Application Support works with all major browsers and dozens of instant messengers, E-mail clients, games, and more!

  19. BTW, is there an easy way to detect that this iframe that you create was created to run the script and not just a regular iframe that the script happens to be inside?

    I find that location.href inside the iframe actually holds the URL of the parent page (maybe to get around cross-domain issues).

  20. [...] Load JS in iframe (acc. to Stoyan/Meebo’s approach) [...]

  21. Hi! I know this is kind of off topic but I was wondering which blog platform are you using for this website? I’m getting sick and tired of WordPress because I’ve had issues with hackers and I’m looking at options for another platform. I would be fantastic if you could point me in the direction of a good platform.|

  22. [...] Non-onload-blocking async JS by Stoyan Stefanov [...]

  23. Hi! Stoyan, is the tag with async attribute still blocking `onload` ?
    sorry my english is not good.

  24. Just came up with this solution a few days ago:

    https://gist.github.com/jishak/5198361#file-async-iframe-demo-html

  25. Bon travail, C’est vraiment bien. Est ce qu’il y a des forums que tu recommandes sur le sujet ?

  26. Hi there, There’s no doubt that your site could possibly be having internet browser
    compatibility problems. Whenever I look at your site in Safari, it looks
    fine but when opening in Internet Explorer, it has some overlapping issues.
    I merely wanted to give you a quick heads up!
    Besides that, excellent website!

  27. I am sure this paragraph has touched all the internet visitors,
    its really really good poszt on building uup new web site.

  28. I was recommended this web site by means of my cousin.
    I am no longer positve whether this post is written by him as
    no one else know such designated about my
    difficulty. You’re amazing! Thank you!

  29. Hello mates, how is everything, and what you desire to say about this paragraph, inn my view its truly awessome designed for me.

  30. I do not even know how I ended up here, however I believed this
    submit was once good. I don’t realize who you might be however certainly you are going to a well-known blogger when
    you are not already. Cheers!

  31. They do exist, and have been proven to work for some people.

    The Piriformis muscle which is a common cause of sciatica, can get irritated with exercise that jars your body.
    Thepresent generation however is slowly but surely waking up to the fact that forsolutions to our problems we have to once more turn to her.

  32. nice idea, but isn’t there a problem about caching with this snippet? I mean the browser never caches the asyncjs1.php in the example according to the chrome inspectore network tabs..what you think?

Leave a Reply