Create a Basic Loader with JavaScript Promises

By  on  

I've used JavaScript loaders for years; whether it was the Dojo loader, curl.js, or even using jQuery as a JavaScript loader, it's incredibly useful to request a set of resources and react once they've completed loading.  Each JavaScript loader is feature-packed, efficient, and does a wonderful job of shimming the Promise API which didn't exist in the browser when the loader is created.  The following is not that type of loader.

This super simple loader allows for loading of image, CSS, and JavaScript files, using the Promise API, and fires a callback upon success or failure.  This tiny "loader" (I shouldn't even call it that) does not:

  • cache results (though that would be easy)
  • provide a module/object back
  • do AJAX calls (though a XHR-to-Promise shim is available, or you can use fetch)
  • ... or anything else advanced

Here is the tiny "loader" in all of its glory:

var load = (function() {
  // Function which returns a function: https://davidwalsh.name/javascript-functions
  function _load(tag) {
    return function(url) {
      // This promise will be used by Promise.all to determine success or failure
      return new Promise(function(resolve, reject) {
        var element = document.createElement(tag);
        var parent = 'body';
        var attr = 'src';

        // Important success and error for the promise
        element.onload = function() {
          resolve(url);
        };
        element.onerror = function() {
          reject(url);
        };

        // Need to set different attributes depending on tag type
        switch(tag) {
          case 'script':
            element.async = true;
            break;
          case 'link':
            element.type = 'text/css';
            element.rel = 'stylesheet';
            attr = 'href';
            parent = 'head';
        }

        // Inject into document to kick off loading
        element[attr] = url;
        document[parent].appendChild(element);
      });
    };
  }
  
  return {
    css: _load('link'),
    js: _load('script'),
    img: _load('img')
  }
})();

// Usage:  Load different file types with one callback
Promise.all([
    load.js('lib/highlighter.js'), 
    load.js('lib/main.js'), 
    load.css('lib/highlighter.css'),
    load.img('images/logo.png')
  ]).then(function() {
    console.log('Everything has loaded!');
  }).catch(function() {
    console.log('Oh no, epic failure!');
  });

A load object is created  with js, css, and img functions which accept a URL to load.  Each function returns a Promise and the onload or onerror event of the resource's tag triggers resolve or reject for the promise.  Promise.all collects the resources to be loaded and then triggers upon successful load of all resources, catch if any of them fail.

I have to stress that this is meant to be a very, very simple "loader"; please save the comments about how it doesn't have bells and whistles that other loaders have. I love how awesome the Promise API makes async and resource loading management, as does the ServiceWorker API and fetch API.  Do yourself a favor and check out these awesome APIs!

Recent Features

  • By
    Create a CSS Flipping Animation

    CSS animations are a lot of fun; the beauty of them is that through many simple properties, you can create anything from an elegant fade in to a WTF-Pixar-would-be-proud effect. One CSS effect somewhere in between is the CSS flip effect, whereby there's...

  • By
    How to Create a Twitter Card

    One of my favorite social APIs was the Open Graph API adopted by Facebook.  Adding just a few META tags to each page allowed links to my article to be styled and presented the way I wanted them to, giving me a bit of control...

Incredible Demos

  • By
    Telephone Link Protocol

    We've always been able to create links with protocols other than the usual HTTP, like mailto, skype, irc ,and more;  they're an excellent convenience to visitors.  With mobile phone browsers having become infinitely more usable, we can now extend that convenience to phone numbers: The tel...

  • By
    CSS Gradients

    With CSS border-radius, I showed you how CSS can bridge the gap between design and development by adding rounded corners to elements.  CSS gradients are another step in that direction.  Now that CSS gradients are supported in Internet Explorer 8+, Firefox, Safari, and Chrome...

Discussion

  1. Great article! Would love to see this on a CDN somewhere. This wouldn’t be a bad way to go for smaller projects that don’t need all the bells and whistles of a full blown build system.

  2. Isn’t making async as default for you js scripts a bit risky. In your example, you might end up parsing “main.js” earlier that “highlighter.js” which might lead to ReferenceErrors thrown around.

    • You’re right; maybe I can add a second argument to load.js where someone can say they want something sync.

  3. samarjit

    Have a declarative set of script dependencies. Look at require.js config shim. Having only this bit makes it scalable.

    requirejs.config({
        shim: {
            'backbone': {
                //These script dependencies should be loaded before loading
                //backbone.js
                deps: ['underscore', 'jquery'],
               }
         }
    });
  4. Max

    I have investigated this topic more extensively when I wrote my own loader (http://w3core.github.io/import.js/) and I see one serious gap here.

    Unfortunately, we can not trust for the “load” event of the “link” tag, because most of mobile browsers does not dispatch the “load” event for this tag. A large number of mentions for this issue you can find on the stackoverflow site. There is no universal solution that can inform us that stylesheet is realy loaded, BUT we can be informed when HTTP request is completed and this is better then waiting of the event that never will be dispatched.

  5. Max

    There is a example that describes technique of event handling:

     var callback = function(){
       alert("stylesheet request complete");
     };
     var url = "//path/to/stylesheet.file";
     var link = document.createElement("img");
     link.addEventListener("error", callback, !1);
     link.src = url;
    

    Practically same loader can be implemented without any jQuery/Promise/etc toolkits (there is an example: http://w3core.github.io/import.js/). It can be more powerful, flexible and simple. Trust me.

    • This is short and sweet, but wouldn’t you also want to know if it failed (for more complex implementations), something which only Promises can give you.

  6. Dirk

    Although not that small it might still be an alternative https://github.com/dlueth/qoopido.demand/tree/feature/genie?files=1 which I wrote some time ago.

  7. Brian

    Hi. This article is linked from https://polyfill.io/v2/docs/examples as an example of using a script-loader to be sure polyfill library is loaded before calling other code.
    Would this be a good strategy, considering this loader uses Promise, which is one of the functions that the Polyfill is enabling for IE?

  8. Wonderful article!
    I’ve taken this a step further with ES2015 classes and static context:
    https://codepen.io/aternus/pen/GdeGqL

  9. I actually wrote a little script loader 5.5 years ago it’s at https://gist.github.com/dexygen/6061573 when all the scripts are loaded it will “run” the designated one

Wrap your code in <pre class="{language}"></pre> tags, link to a GitHub gist, JSFiddle fiddle, or CodePen pen to embed!