Oliver Nassar

readystate firing for dynamically loaded scripts

April 07, 2011

I've gotten in the habit of loading external JS by booting it in dynamically/asynchronously after the page has loaded, however in doing so I stumbled upon an event difference between browsers that was news to me.

In Firefox (older version I believe) and IE, the readyState event won't fire (and actually doesn't exist as an attribute of the script DOM object) after a resource has been loaded in. This caused an issue with my booter, which I addressed as follows:

if (script.readyState) {
    script.onreadystatechange = function() {
        if (
            script.readyState === 'loaded'
            || script.readyState === 'complete'
        ) {
            script.onreadystatechange = null;
            loaded();
        }
    };
} else {
    script.onload = loaded;
}

The full code for the booter is:

var boot = function(assets, callback) {

    // booter
    var __boot = function(src, callback) {
        var script = document.createElement('script'),
            scripts = document.getElementsByTagName('script'),
            length = scripts.length,
            loaded = function() {
                try {
                    callback && callback();
                } catch(exception) {
                    log('[Caught Exception]', exception);
                }
            };
        script.setAttribute('type', 'text/javascript');
        script.setAttribute('charset', 'utf-8');
        if (script.readyState) {
            script.onreadystatechange = function() {
                if (
                    script.readyState === 'loaded'
                    || script.readyState === 'complete'
                ) {
                    script.onreadystatechange = null;
                    loaded();
                }
            };
        } else {
            script.onload = loaded;
        }
        script.setAttribute('src', src);
        document.body.insertBefore(script, scripts[(length - 1)].nextSibling);
    };

    // assets check
    if (typeof assets === 'string') {
        __boot(assets, callback);
    } else if (assets.constructor === Array) {
        if (assets.length) {
            __boot(assets.shift(), function() {
                boot(assets, callback);
            });
        } else {
            try {
                callback && callback();
            } catch(exception) {
                log('[Caught Exception]', exception);
            }
        }
    }
}

I'm a fan of this script because it allows me to:

Boot in single assets

boot(
    'https://ajax.googleapis.com/ajax/libs/mootools/1.3.1/mootools-yui-compressed.js',
    function() {
        // do stuff
    }
);

Boot in an array of assets

boot(
    [
        'https://ajax.googleapis.com/ajax/libs/mootools/1.3.1/mootools-yui-compressed.js',
        'https://ajax.googleapis.com/ajax/libs/mootools/1.3.1/mootools-yui-compressed.js'
    ],
    function() {
        // do stuff
    }
);