# defer.js #
**Async Everything**
defer.js is a tiny boot-strapper that allows you to make all loading of JavaScript on your page asynchronous. Yes, everything.
Packed in less than 2kB (gzipped), defer.js is a predicate-based execution engine. You give it a rule, and once the condition is met, any hunk of JS you've got will be run. It's that simple.
Using predicates to let all your JS load asynchronously is just a welcome side-effect.
An example:
defer( {
predicate: function(){ return condition === true; } ,
handler: function(){ runThisCode(); } ,
options: {}
} );
// you can load the external script you just referenced, even after-the-fact
## Huh? ##
* Load any script you'd like asynchronously, even libraries like jQuery
* With all-async code, the 'meat' of your page will **load faster**, especially on mobile devices. Text and images don't have to wait for scripts before loading.
* Put code on the page where you need to, even if that's *before your libraries!*
## Why async & deferred JS is important ##
**Make pages feel _faster_.**
Loading your code asynchronously allows your browser to load every resource it can as quickly as possible, often in parallel. This includes images, stylesheets, etc. It also means no blocking — your browsing experience needn't wait for code you may not need yet.
Google's Page Speed documentation explains the benefits of [asynchronous](http://code.google.com/speed/page-speed/docs/rtt.html#PreferAsyncResources) and [deferred](http://code.google.com/speed/page-speed/docs/payload.html#DeferLoadingJS) loading in more technical detail.
## Origin ##
A year ago, I was considering how Google Analytics stores data in-page before its own code had loaded. If you've never seen it before, it looks like this:
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-XXXXXXX-X']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
By cleverly abusing an array, they store your configuration information while only using a single, oddly-named global variable. Not bad.
On a separate tangent, I had stuck in my head a problem that popped up in a recent project. The site in question broke various page elements into components. Sometimes, data was fetched in the execution of these components, and needed to be passed to JS on that page. Someone proposed that we load the data via AJAX, but that seemed horrible. We didn't need a second HTTP roundtrip, or a second fetch of data from the DB. Data attributes weren't (and still aren't) sufficiently widespread to be of use. We threw the data in a custom object, in-page, referenced later by other code and called it a day.
We had wasted a global variable (not ideal), but it worked. Of course, this only worked for storing simple data, unless the massive site code got loaded first. Doing so isn't great for performance.
I wanted a way to defer executing that code until the framework code it depended on was good and ready. To make it universal, a custom function's return value could be used as the predicate.
The first draft of defer.js was all about predicates. Then, I realized we could use those facilities to let defer.js load itself.
##Wait, it loads itself? ##
More or less, yes.
defer.js uses a single global variable for everything: window.defer. If this code has been loaded, you can use it just as in the example above. If not, you can still use it right now.
defer.js uses a variant on the abuse-an-array idea, but to a higher degree. Once defer.js loads, it runs all the things you've put into the array, *then it becomes the array*.\*
Yes, that totally works.
\* JS doesn't offer operator overloading, so defer.push() is the only way in which you'll interact with defer.js like an array. None of that bracket business.
Note that we use window.defer instead of simply defer, since this is more reliable inside of closures, and in certain browsers.
Also note the use of single-character shorthands for predicate, handler, and options. You're welcome.
## Are you ready? ##
defer.js sure is. It loads code as soon as the DOM is manipulable, much like jQuery's $.ready().
## Out of Order? ##
By the time you ask the browser to evaluate any JavaScript, any other code it depends on must already be loaded. In a pre-asynchronous JS world, knowing what to load and when was easy enough (unless you had little control over the page's overall HTML). When you use use async out-of-the-box, things aren't so easy. It's tougher to know which will load first, a given chunk of code or the code it depends on.
If your dependent code tries to load first, you'll get a nasty error and likely a broken page. This out-of-order error is a race condition. With defer.js, it's also a thing of the past.
As long as your predicate logic is sound, you'll never again run afoul of an async-based race condition. At worst, it'll timeout (which you can adjust, and indicates issues in some other part of your infrastructure). A nice side-effect of defer.js's failure handling is that, for the first time, you can recover from code loading failures without the use of XMLHttpRequest.
## Compatibility? ##
Yes.
If it's a browser, it'll work. defer.js has been tested on every browser I can get my mitts on, even those that don't support asynchronous loading of JS. In those cases, it does no harm.
That said, I assume no liability for the use of defer.js. Read the license for more details.
## Resources ##
Besides its sub-2kB download size, defer.js is very respectful of browser resources. defer.js only runs when you ask it to, and does not leave any timers or event handlers 'dangling' thereafter.
As with all static scripts, be sure to [set your cache-related headers correctly](http://code.google.com/speed/page-speed/docs/caching.html). Even 1kB is 1kB too much when it could be in cache.
## Try it out ##
Want to see it in action, against loading an image and jQuery? [Test defer.js now!](http://wessman.github.com/defer.js/test/test0.html)
Pay special attention to the image load time & order. Note that the load times for both defer.js and jQuery in these test pages are based on the DOM being ready. Use Webkit Inspector's Timeline or your browser's equivalent to verify the numbers yourself.
Judiciously clearing your cache, along with manipulating your connection (I use Network Link Conditioner) will give you a more complete picture of defer.js's performance.
The benefits of defer.js are most pronounced on real-world-sized pages, and on slower network links (like mobile).
## Methods ##
defer( {p:... , h:... , o:...} )defer.push( {p:... , h:... , o:...} )defer.cancel( sequenceID )defer() method, and you can cancel a deferred block of code. This is not guaranteed to prevent execution, that depends on the runloop's whims.defer.version()defer.isReady()timeoutintervalonFailthis isn't safe to use here.defer.log()\dev\null. If you use the debug version, errors will route correctly to the browser's JS console. Easy.defer.isNil( objToTest )undefined or null. Returns false if it's a dud.defer.isFunction( functionToTest )false if not.defer.forOwnIn( context , dictionary , handler )hasOwnProperty stuff. The context is this, and dictionary is your object. The function you pass to handler has two parameters, key and value. Example:
defer.forOwnIn(
this , // context
{ 'key1' : 'val1' , 'key2' : 'val2' } , // dictionary
function( key , value ){
defer.log( key + "=" + value );
}
);
defer.appendScript( src , async )<script> tag to the end of the page's body. The async parameter accepts either async or defer.| Loader | min+gzip | min+gzip+license | Async Itself | Dependency Ordering | Dependency-Free | JS-Free Load |
|---|---|---|---|---|---|---|
| defer.js | 1.4kB | 1.8kB | Yes! | Yes (use predicates) | Yes! | Yes! |
| bdLoad | 3.7kB | 4.7kB | No | Yes | Yes | No |
| Bootstrap | 0.7kB | 1.1kB | No | No | Yes | No |
| BravoJS | 6.5kB | 7.2kB | No | Yes | Yes | No |
| Curl.js* | 3.5kB | 4.3kB | No | Yes | Yes | No |
| ControlJS | 1.9kB | 2.2kB | No | Yes | Yes | No |
| dominatejs | 11.4kB | 12kB | No | Yes | Yes | No |
| head.js | 1.3kB | 2.1kB | No | Yes | Yes | No |
| JSL | 0.8kB | 1.5kB | No | Yes | Yes | No |
| JSLoad | 1.1kB | 1.6kB | No | Yes | Yes | Yes |
| jQl | 0.8kB | 1.3kB | Sort of | Yes | Yes | No |
| LABjs | 2.3kB | 2.9kB | Sort of | Yes | Yes | No |
| LazyLoad | 1kB | 1.7kB | No | Yes | Yes | No |
| NBL | 0.6kB | 1.3kB | No | Yes | Yes | No |
| RequireJS | 5.5kB | 6.2kB | No | Yes | Yes | No |
| $script.js | 0.9kB | 1.5kB | No | Yes | Yes | No |
| Steal | 4kB | none specified | No | Yes | Yes | No |
| yepnope.js | 1.7kB | 1.7kB | No | Yes | Yes | No |
| YUI 2 Loader | 9.9kB | 10kB | No | Yes | Yes | No |
| YUI 3 Seed | 20.7kB | 20.7kB | No | Yes? | Yes | No |