DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
  1. DZone
  2. Coding
  3. JavaScript
  4. JavaScript Asynchronous Dependency Loader

JavaScript Asynchronous Dependency Loader

Dmitry Sheiko user avatar by
Dmitry Sheiko
·
Mar. 13, 13 · Interview
Like (1)
Save
Tweet
Share
6.53K Views

Join the DZone community and get the full member experience.

Join For Free

A substantial web application doesn't need to wait until all the required JavaScript libraries loaded. Usually most of them can load asynchronously and start acting whenever they are ready. Most commonly used approach here would be AMD. That's a sophisticated and time-proved solution. However to use it with libraries, you must have them converted to modules. I don't appreciate the idea to interfere with 3-rd party library code. At the same time I still need non-blocking loading and dependency being resolved in my code. Thus, I worked out a very simple, but yet working solution.

Non-AMD libraries usually expose public API by providing accessor(s) in global scope. So, to resolve this kind of dependency we can find the exposed API in global scope as soon as the dependency script is loaded. Therefore we just need a handler subscribed for the event, which is being fired when the dependency script is loaded. It'll serve, though polluting the global scope isn't a good practice to adopt. On the contrary, while writing our own code, we can use CommonJS-like module pattern:

(function( exports ){
exports.moduleA = { /*..*/ };
exports.moduleN = { /*..*/ };
}( module ));
console.log ( typeof module.moduleA  );

Keeping this in mind, I came up with the following API:

// Load /js/form.js asynchronously and make it a dependency named as form
rjs.define("/js/form.js", "form");
// Call the handler when both events DOMContentLoaded and form dependency loaded are fired
rjs.require(["DOMContentLoaded", "form-loaded"], function( module ){
});

Here, unlike AMD, rjs.require refers not to dependencies, but to the involved events. Thus we imply that we want the handler to invoke when all the supplied events fired, provided that dependency onLoad events named as -loaded. It makes more sense since, in fact, we are interested in the events. Besides, we can refer not only to dependency loaded events, but to others such as DOMContentLoaded.

Next goes the implementation in VanillaJs (JavaScript 1.6):

var module = module || {},
    rjs = (function( global, exports ){
   "use strict";
    var document = global.document,
        queue = {},
        /**
         * Firing event helper
         *
         * @param {string} eventName
         * @param {array} payload
         */
        triggerEvent = function( eventName, payload ) {
            var e = document.createEvent("Event");
            e.initEvent( eventName, true, true );
            e.payload = payload;
            document.dispatchEvent( e );
        },
        /**
         * Subscribe helper to an event helper
         *
         * @param {string} eventName
         * @param {function} fn
         */
       onEvent = function( eventName, fn ) {
            document.addEventListener( eventName, function( e ){
                fn.apply( null, e.payload || [] );
            }, false );
        },
       eventQueue = (function(){
           return new function() {
                var queue = [];
                return {
                    /**
                     * Register a supplied event to the queue
                     *
                     * @param {string} eventName
                     */
                    register: function( eventName ) {
                        queue.indexOf( eventName ) === -1 && queue.push( eventName );
                    },
                    /**
                     * Checks if all the events of a supplied array already registered
                     * in the queue
                     *
                     * @param {array} events
                     */
                    isResolved: function( events )  {
                        var i = 0, len = events.length;
                        if (!len) {
                            return false;
                        }
                        for ( ; i < len; i++ ) {
                            // If at least one event of the supplied list is not
                            // yet fired, the queue is not resolved
                            if ( queue.indexOf( events[ i ] ) === -1 ) {
                                return false;
                            }
                        }
                        return true;
                    }
                }
            };
       }());

       return (function(){
            // Register DOMContentLoaded event
           onEvent( "DOMContentLoaded", function(){
               eventQueue.register("DOMContentLoaded");
           });
           return {
                /**
                 * Load a given script asynchronously and fires event <dependency>-load
                 * when script is loaded and both DOM are ready and script is loaded
                 *
                 * @param {string} file - dependency script path
                 * @param {string} dependency - dependency name
                 * @param {function) completeFn OPTIONAL - A callback function
                 *      that is executed when the request completes.
                 */
                define: function( file, dependency, completeFn ) {
                    var script = document.createElement("script");
                    script.type = "text/javascript";
                    script.src = file;
                    document.body.appendChild( script );
                    script.addEventListener( "load", function(){
                        var eventName = dependency + "-loaded";
                        // Registers event in the queue
                        eventQueue.register( eventName );
                        // Fires event when the script is loaded
                        triggerEvent( eventName, [ exports ] );
                        completeFn && completeFn();
                    }, false );
                },
                /**
                 * Call the function fn when all supplied events fired
                 *
                 * @param {array} dependencies
                 * @param {function} fn - callback function that is executed
                 *  when all the supplied dependencies resolved
                 */
                require: function( events, fn ) {
                     // Event fired before a handler subscribed
                    if ( eventQueue.isResolved( events ) ) {
                         return fn( module );
                    }
                    // Event fired after a handler subscribed
                    events.forEach(function( eventName ){
                        onEvent( eventName, function( module ){
                            eventQueue.isResolved( events ) && fn( module );
                        });
                    });
                }
           };
       }());

}( this, module ));

When using JQuery the code looks a bit more succinct as the library provides event-handling helpers. Moreover, it is supposed to be supported by legacy browsers. Here's the port:

var module = module || {},
    rjs = (function( global, exports ){
   "use strict";
    var document = global.document,
        $ = global.jQuery,
        queue = {},
       eventQueue = (function(){
           return new function() {
                var queue = [];
                return {
                    /**
                     * Register a supplied event to the queue
                     *
                     * @param {string} eventName
                     */
                    register: function( eventName ) {
                        $.inArray( eventName, queue ) === -1 && queue.push( eventName );
                    },
                    /**
                     * Checks if all the events of a supplied array already registered
                     * in the queue
                     *
                     * @param {array} events
                     */
                    isResolved: function( events )  {
                        var i = 0, len = events.length;
                        if (!len) {
                            return false;
                        }
                        for ( ; i < len; i++ ) {
                            // If at least one event of the supplied list is not
                            // yet fired, the queue is not resolved
                            if ( $.inArray( events[ i ], queue ) === -1 ) {
                                return false;
                            }
                        }
                        return true;
                    }
                }
            };
       }());
       return (function(){
            // Register DOMContentLoaded event
           $( document ).on( "DOMContentLoaded", function( ){
               eventQueue.register("DOMContentLoaded");
           });
           return {
                /**
                 * Load a given script asynchronously and fires event <dependency>-load
                 * when script is loaded and both DOM are ready and script is loaded
                 *
                 * @param {string} file - dependency script path
                 * @param {string} dependency - dependency name
                 * @param {function) completeFn OPTIONAL - A callback function
                 *      that is executed when the request completes.
                 */
                define: function( file, dependency, completeFn ) {
                    var script = document.createElement("script");
                    script.type = "text/javascript";
                    script.src = file;
                    document.body.appendChild( script );
                    $( script ).on( "load", function(){
                        var eventName = dependency + "-loaded";
                        eventQueue.register( eventName );
                        // Fires event when the script is loaded
                        $( document ).trigger( eventName, [ exports ] );
                        completeFn && completeFn();
                    });
                },
                /**
                 * Call the function fn when all supplied events fired
                 *
                 * @param {array} dependencies
                 * @param {function} fn - callback function that is executed
                 *  when all the supplied dependencies resolved
                 */
                require: function( events, fn ) {
                    // Event fired before a handler subscribed
                    if ( eventQueue.isResolved( events ) ) {
                         return fn( module );
                    }
                    // Event fired after a handler subscribed
                    events.forEach(function( eventName ){
                        $( document ).on( eventName, function( e, module ){
                            eventQueue.isResolved( events ) && fn( module );
                        });
                    });
                }
           };
       }());
}( window, module ));

The library is released under MIT license at github.com/dsheiko/micro-requirejs

Dependency JavaScript Loader (equipment)

Published at DZone with permission of Dmitry Sheiko, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • AWS Cloud Migration: Best Practices and Pitfalls to Avoid
  • Easy Smart Contract Debugging With Truffle’s Console.log
  • Using the PostgreSQL Pager With MariaDB Xpand
  • Core Machine Learning Metrics

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: