Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

https://github.com/wessman/defer.js

Async Everything: Make the meat of your pages load faster with this JS morsel
https://github.com/wessman/defer.js

javascript-async

Last synced: about 2 months ago
JSON representation

Async Everything: Make the meat of your pages load faster with this JS morsel

Lists

README

        

# 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*.\*


window.defer = window.defer || []; // in case defer.js hasn't loaded yet
window.defer.push( { // oddly familiar
p: function(){ return condition === true; } ,
h: function(){ runThisCode(); } ,
o: {}
} );

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:...} )

Use this to create a new item to defer. Returns a unique sequence ID number.

defer.push( {p:... , h:... , o:...} )

The asynchronous way to defer your code, as if you were adding to an array.

defer.cancel( sequenceID )

Pass in the sequence ID from the non-asynchronous 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()

Returns a string of defer.js's version.

defer.isReady()

Returns a boolean indicating whether the DOM is ready for manipulation.

## Options ##

options: { timeout: 30000 , interval: 100 , onFail: function(){ alert("failed"); } }


timeout

How long, in milliseconds, until defer.js gives up on running your handler. The default is 15000ms (15 seconds).

interval

How long, in milliseconds, between attempts to validate your predicate. The default is 50ms.

onFail

Your very own custom failure handler. If deferred your code within a closure, you can use your own references and variables in here. Note that this isn't safe to use here.

## Sugar ##

There's *more?*

defer.js uses a few small helper functions, which have been externalized for your convenience.



defer.log()



This is a safe way to reference console.log, even in browsers that don't support the console. If you're using a production version of defer.js, your logs go straight to \dev\null. If you use the debug version, errors will route correctly to the browser's JS console. Easy.


defer.isNil( objToTest )



Test if the first parameter is undefined or null. Returns false if it's a dud.


defer.isFunction( functionToTest )



Test if the first parameter is a usable function. Returns false if not.


defer.forOwnIn( context , dictionary , handler )



Use this for testing every property on a custom dictionary/hash object without typing the whole 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 )



If you want to load code asynchronously, in code, use this. It simply attaches a <script> tag to the end of the page's body. The async parameter accepts either async or defer.

## Compared To... ##

I looked at a few projects with vaguely similar aspirations before starting this project. I didn't want to re-invent the wheel.

Nothing I found had the combination of super-small load size, conceptual simplicity, zero dependencies, and pervasive asynchronicity.

Since then, a number of others JS loaders have popped up. Here's a little comparison:



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

Honestly, I had no idea many of these loaders existed until I was revising this README. My thanks to Simone D'Amico and Éric Daspet, whose lists of JS loaders inspired the table above.

defer.js doesn't offer the kind of extensive dependency ordering that a lot of the others offer, for two reasons. First, it offers a new way around the async loading race condition that makes these other systems necessary. Second, defer.js exists to speed loading on the majority of web pages, and is not optimized for the Photoshop-in-the-browser class of web applications (though it ought to be of good use there, too!).

Regarding the filesizes listed: Some weren't minimized, so I minimized them using the Closure Compiler, at the highest level of optimization that wouldn't throw warnings or errors. Also, quite a few projects didn't include valid license boilerplate, so I included it where necessary. Specifically, MIT- and GPL-licensed code requires at least the boilerplate attached to each and every file. Persuant to these licenses, you technically have no rights to use the code without the proper boilerplate attached. (I am not a lawyer.)

## License, Copyright & Trademark ##

defer.js is made available under the Apache License, Version 2.0.

All code in this repository is copyright 2011-2012 Ian J. Wessman.

"defer.js" and the defer.js logo are trademarks of Ian J. Wessman.