Asynchronous Script Loading Callback

33,887

Solution 1

Thanks RASG for https://stackoverflow.com/a/3211647/982924

Async function with callback:

function async(u, c) {
  var d = document, t = 'script',
      o = d.createElement(t),
      s = d.getElementsByTagName(t)[0];
  o.src = '//' + u;
  if (c) { o.addEventListener('load', function (e) { c(null, e); }, false); }
  s.parentNode.insertBefore(o, s);
}

Usage:

async('snapabug.appspot.com/snapabug.js', function() {
    SnapABug.init('XXXXX-XXXXX-XXXXX-XXXXX-XXXXX');
});

jsFiddle

Solution 2

A more recent snippet:

async function loadAsync(src) {
    const script = document.createElement('script');
    script.src = src;
    return new Promise((resolve, reject) => {
        script.onreadystatechange = function () {
            if (script.readyState === 'loaded' || script.readyState === 'complete') {
                script.onreadystatechange = null;
                resolve(true);
            }
        };
        document.getElementsByTagName('head')[0].appendChild(script);
    });
}

utilisation

  loadAsync(`https://....js`).then(_ => {
    //  ... script loaded here
  })

James Kyle's answer doesn't take IE9 into account. Here is a modified version of the code I found in the link proposed in the comments. Modify the var baseUrl so it can find the script accordingly.

//for requiring a script loaded asynchronously.
function loadAsync(src, callback, relative){
    var baseUrl = "/resources/script/";
    var script = document.createElement('script');
    if(relative === true){
        script.src = baseUrl + src;  
    }else{
        script.src = src; 
    }

    if(callback !== null){
        if (script.readyState) { // IE, incl. IE9
            script.onreadystatechange = function() {
                if (script.readyState === "loaded" || script.readyState === "complete") {
                    script.onreadystatechange = null;
                    callback();
                }
            };
        } else {
            script.onload = function() { // Other browsers
                callback();
            };
        }
    }
    document.getElementsByTagName('head')[0].appendChild(script);
}

utilisation:

loadAsync('https://www.gstatic.com/charts/loader.js' , function(){
    chart.loadCharts();
    });
// OR relative path
loadAsync('fastclick.js', null, true);

Solution 3

The other answers works well, but aren't super readable or require Promises. Here is my two cents:

function loadScript(src, callback) {
    var script = document.createElement('script');
    script.setAttribute('src', src);
    script.addEventListener('load', callback);
    document.head.appendChild(script);
},
Share:
33,887
James Kyle
Author by

James Kyle

Babel & Flow conspirator. Lerna maintainer. Engineer at Facebook. Previously CloudFlare. The Real Beyonce of JavaScript.

Updated on April 14, 2020

Comments

  • James Kyle
    James Kyle about 4 years

    http://jsfiddle.net/JamesKyle/HQDu6/

    I've created a short function based on Mathias Bynens Optimization of the Google Analytics asynchronous script that goes as following:

    function async(src) {
      var d = document, t = 'script',
          o = d.createElement(t),
          s = d.getElementsByTagName(t)[0];
      o.src = '//' + src;
      s.parentNode.insertBefore(o, s);
    }
    

    This works great and I've already started using it for several different scripts

    // Crazy Egg
    async('dnn506yrbagrg.cloudfront.net/pages/scripts/XXXXX/XXXXX.js?' + Math.floor(new Date().getTime() / 3600000));
    
    // User Voice
    var uvOptions = {};
    async('widget.uservoice.com/XXXXX.js');
    
    // Google Analytics
    var _gaq = [['_setAccount', 'UA-XXXXX-XX'], ['_setDomainName', 'coachup.com'], ['_trackPageview']];
    async('google-analytics.com/ga.js');
    
    // Stripe
    async('js.stripe.com/v1');​
    

    The problem comes when I encounter a script that needs to be called after it's loaded:

    // Snap Engage
    async('snapabug.appspot.com/snapabug.js');
    SnapABug.init('XXXXX-XXXXX-XXXXX-XXXXX-XXXXX');
    

    So I figured I'd turn this into a callback function that would be used as so:

    async('snapabug.appspot.com/snapabug.js', function() {
        SnapABug.init('XXXXX-XXXXX-XXXXX-XXXXX-XXXXX');
    });
    

    I did not expect that this would be difficult for me to do but it has turned out that way.

    My question is what is the most efficient way to add a callback without overcomplicating the code.

    See the jsfiddle: http://jsfiddle.net/JamesKyle/HQDu6/

  • aefxx
    aefxx about 10 years
    The line o.u = '//' + u; should read o.src = '//' + u; or else it won't load a single byte. Minifying can be tricky at times.
  • baohouse
    baohouse over 9 years
    s.addEventListener should be o.addEventListener
  • Dave Cahill
    Dave Cahill about 9 years
    I think this needs more logic to deal with IE8 and before, see for example: aaronpeters.nl/blog/prevent-double-callback-execution-in-IE9
  • Trev14
    Trev14 about 7 years
    Nice work & thanks for the great answer. Just change "doc" in the last line of the function to "document".
  • Kevin Farrugia
    Kevin Farrugia over 6 years
    I think this solution is more comprehensive and includes a Promise implementation too: stackoverflow.com/questions/7718935/load-scripts-asynchronou‌​sly