Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Asynchronous Script Loading Callback

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/

like image 780
James Kyle Avatar asked Oct 10 '12 13:10

James Kyle


People also ask

Can callbacks be asynchronous?

Callbacks are not asynchronous by nature, but can be used for asynchronous purposes. In this code, you define a function fn , define a function higherOrderFunction that takes a function callback as an argument, and pass fn as a callback to higherOrderFunction .

How do I load asynchronous scripts?

The simplest solution is to keep all of your scripts inline at the bottom of the page, that way they don't block the loading of HTML content while they execute. It also avoids the issue of having to asynchronously load each required script.

Is callback synchronous or asynchronous?

There are 2 kinds of callback functions: synchronous and asynchronous. The synchronous callbacks are executed at the same time as the higher-order function that uses the callback. Synchronous callbacks are blocking.

What is the purpose of a callback in an asynchronous function?

Asynchronous callbacks are functions passed to another function that starts executing code in the background. Typically, when the code in the background finishes, the async callback function is called as a way of notifying and passing on data to the callback function that the background task is finished.


3 Answers

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

like image 190
James Kyle Avatar answered Oct 23 '22 22:10

James Kyle


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);
like image 13
Ced Avatar answered Oct 23 '22 23:10

Ced


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);
},
like image 10
Fredrik Avatar answered Oct 24 '22 00:10

Fredrik