Non-onload-blocking async JS
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.onloadnorDOMContentLoaded - 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
- create an iframe without setting
srcto a new URL. This firesonloadof the iframe immediately and the whole thing is completely out of the way - style the iframe to make it invisible
- get the last
scripttag so far, which is the snippet itself. This is in order to glue the iframe to the snippet that includes it. - insert the iframe into the page
- get a handle to the
documentobject of the iframe - write some HTML into that iframe document
- this HTML includes the desired script
This entry was posted on Thursday, June 28th, 2012 and is filed under JavaScript, performance. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.
Get notification for future posts: follow me on Twitter or subscribe to my RSS feed

June 28th, 2012 at 4:08 am
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?
June 28th, 2012 at 11:27 am
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.
June 28th, 2012 at 11:29 am
Sorry my typo above, the correct link to Caridy’s Twitter is http://twitter.com/caridy.
* feature request: edit after posting comments
June 28th, 2012 at 4:51 pm
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
June 28th, 2012 at 5:33 pm
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.
June 29th, 2012 at 4:11 am
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
June 29th, 2012 at 4:26 am
@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
June 29th, 2012 at 9:30 am
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?
July 2nd, 2012 at 12:28 pm
I have been looking for this technique since long time. Thanks for the post.
July 6th, 2012 at 3:18 pm
@Warren, yes, anything you do like:
window.onload = function(){/* stuffs */};is obviously not going to block `onload`
July 7th, 2012 at 8:35 am
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.
July 19th, 2012 at 4:52 pm
@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.
July 23rd, 2012 at 12:38 pm
@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.
July 23rd, 2012 at 1:29 pm
[...] 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 [...]
August 8th, 2012 at 3:14 am
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();
August 8th, 2012 at 3:16 am
Oops, something happend with include code snippet. A link to test page than:
http://banners.adfox.ru/120808/adfox/215374/test3.Stoyan.html
October 8th, 2012 at 8:32 pm
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
October 16th, 2012 at 10:08 am
Advanced Application Support works with all major browsers and dozens of instant messengers, E-mail clients, games, and more!
October 30th, 2012 at 3:17 pm
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).
December 8th, 2012 at 4:39 am
[...] Load JS in iframe (acc. to Stoyan/Meebo’s approach) [...]
December 9th, 2012 at 2:13 pm
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.|
December 10th, 2012 at 2:14 am
[...] Non-onload-blocking async JS by Stoyan Stefanov [...]
January 20th, 2013 at 6:50 pm
Hi! Stoyan, is the tag with async attribute still blocking `onload` ?
sorry my english is not good.
March 19th, 2013 at 1:50 pm
Just came up with this solution a few days ago:
https://gist.github.com/jishak/5198361#file-async-iframe-demo-html