Javascript micro-loader

Several recent projects have left me wishing for a way to keep track of asnychronous scripts and variables as they become available. In other words, I needed a function that could defer the execution of other functions until specific test conditions had been met. Something along the lines of:

loaded.someOtherFunction(function(){
    alert('someOtherFunction() is ready now');
});

Many scripting frameworks include some kind of deferral functions, but I came up empty when looking for a script that was sufficiently flexible while remaining light enough to keep its own loading time down. So I sat down to write. The solution took me all the way back to 2007, when Peter Michaux’s lazy functions first hit the scene. Besides being ridiculously useful tools for writing cross-browser code or bypassing expensive calculations, lazy functions are also provide a great basis for a dynamic loader.

The general idea here is to implement the loader as a function factory that will return a chameloenic deferral function that can defer other functions until a specific test condition becomes true. It comes out looking a little something like this:

var loaded = {};

loaded.microLoader = function( options ) {

    var callbacks = [];
    var o = options;

    loaded[options.name] = function( callback ) {

        var f;
        var i = 0;

        if( typeof( callback ) === 'function' ) {
            callbacks.push( callback );
        }
        if(o.test()) {
            while( f = callbacks[i++] ) { 
                f(); 
            }       
            loaded[options.name] = function( callback ) {
                if( typeof(callback) === 'function' ) return callback(); 
                return true;
            }
        }
        return false;
    }
    o.init();   
    return loaded[options.name];
}

The loading process works in three steps:

  1. Each time the deferral function is called before or during loading, the function passed as its parameter is stored in the callbacks array.
  2. When loading is complete, the loading function options.init notifies the loader by calling it.
  3. As the loading condition is now true, all stored callback functions are executed and the loader is replaced by a “run-anything” version of itself.

To implement this process, the object passed as an argument to loaded.microLoader must include a parameter identifying the deferral function and the methods that initialize the loader’s payload and test for completion. To create a function (loaded.someOtherFunction()) for deferring some step(s) of execution until a script (“js/someOtherFunction.js”) has been loaded, you might call loaded.microLoader with the following:

loaded.microLoader({
    name: 'someOtherFunction',
    init: function(){ 
        script = document.createElement('script');
        script.onreadystatechange= function () {
            // IE compatibility
            if (this.readyState == 'complete') loaded.someOtherFunction();
        }
        script.onload = loaded.someOtherFunction;
        script.src = 'js/someOtherFunction.js';
        document.body.appendChild(script);
    },
    test: function(){ typeof someOtherFunction != 'undefined' }
});

When a function is passed as the sole argument to the loaded.someOtherFunction() method, it will not be executed until the variable someOtherFunction (defined externally in js/someOtherFunction.js) becomes available.

The same approach can be used to test for a change in any kind of asynchronous process to which a callback might be attached. Simply initiate the process in the init function with a callback to the deferral function for when loading has completed and add an appropriate test to determine if loading has completed. It’s fast, flexible, and weighs in compressed at a scale-tipping 283 bytes.

KKPGK7GDZH2X

Let’s keep in touch

Reach out on Twitter or subscribe for (very) occasional updates.

Hey, I'm RJ: digital entomologist and intermittent micropoet, writing from the beautiful Rose City.