32

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/

1

3 Answers 3

58

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

Sign up to request clarification or add additional context in comments.

4 Comments

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.
s.addEventListener should be o.addEventListener
I think this needs more logic to deal with IE8 and before, see for example: aaronpeters.nl/blog/prevent-double-callback-execution-in-IE9
I think this solution is more comprehensive and includes a Promise implementation too: stackoverflow.com/questions/7718935/load-scripts-asynchronously
14

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);

1 Comment

Nice work & thanks for the great answer. Just change "doc" in the last line of the function to "document".
13

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);
},

1 Comment

Small improvement: If you use script.addEventListener('load', callback,{ once: true }); then the listener will be cleared automatically after the load event has fired.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.