How to detect when an external library has loaded

Sometimes in a web app, you’ll want to load a script externally. The only problem is if you need to reference something within the external library, which you should only do once it’s definitely loaded.

Here’s a tip that works with all browsers to ensure the script is loaded before running your dependant code.

Here’s the JavaScript code to load the external library with a callback passed in:

function loadExtScript(src, callback) {
  var s = document.createElement('script');
  s.src = src;
  document.body.appendChild(s);

  // if loaded...call the callback
}

Firefox allows you to listen for the onload event on the script element:

s.onload = callback;

With Internet Explorer you can wait for a state change on the script element:

s.onreadystatechange = function() {
  if ( this.readyState != "loaded" ) return;
  callback.call();
}

The problem comes with Safari – there’s no event change for Safari, so we can’t tell when the script has loaded.

This is the solution I came up with (and this solution should also work with Opera):

function loadExtScript(src, test, callback) {
  var s = document.createElement('script');
  s.src = src;
  document.body.appendChild(s);

  var callbackTimer = setInterval(function() {
    var call = false;
    try {
      call = test.call();
    } catch (e) {}

    if (call) {
      clearInterval(callbackTimer);
      callback.call();
    }
  }, 100);
}

The function takes a test as a parameter. Since you are the designer of the app, you’ll know what successful test is. Once this test is true, it will execute the callback.

A simple test could be to check whether a function exists, for example:

loadExtScript('/fixpng.js', function() {
  return (typeof fixpng == 'function');
}, myCallbackFunction);

6 Responses to “How to detect when an external library has loaded”

  1. My precedent post wasn’t spam, just another, more simple (imho), solution that works perfectly with one or more external script.
    I hope You don’t think You wrote for first time solutions like this one because there are a lot of alternatives, mine is only one of them.
    Regards

  2. Sorry it got nuked – you just posted a link so I wasn’t sure if there was any more to it. I took another look at your solution, but the crux of what I’m proposing is to load an external script – but then fire a callback once it’s loaded (i.e. that callback may run code that’s within the external script).

    The point in my post is that Safari doesn’t fire a load event, so this post shows one way to detect when the library is loaded, to then fire your callback.

    The only other way I know is to add a second script element moments after the first has been loaded to fire the callback.

  3. [...] simply give no indication of this at all. One common solution to dealing with this is to use an interval and perform a check. The work at TrainOfThoughts however takes the beautiful approach of inserting [...]

  4. [...] The following code, if executed from the lower text area of the MochiKit interpreter, will define a function that can be subsequently used to import other JS files. It is based on this post in the MochiKit Trac system. The ‘test’ parameter may be omitted in IE or FF; its purpose is described here. [...]

  5. It is still worth mentioning that some versions of IE contain a bug that could cause an Operation Aborted error if the injected content is added to the BODY before the element is fully parsed (closed), such as when scripts are being loaded before DOM-ready. As a workaround, the SCRIPT element can instead be added to the HEAD to prevent the Operation Aborted error in IE 6-7.

  6. Personally I use the following, can pass addRequiredScripts an Array and a callback once the scripts are “Ready”. Or for individual scripts, loadJavaScript with the javascript url and an optional callback.

    
    var obj = {
    addRequiredScripts: function(scripts, callback){
    		//Bradly SHARPE - Wrapper to include required scripts, scripts can be an array of paths, callback is optional
    		var loaded = 0,
    			haveLoadedScript = false;
    		for (var i=0; i<scripts.length; i++) {
    			var script;
    			if (typeof scripts[i] === 'object') {
    				script = scripts[i].src;
    				if (eval('typeof ' + scripts[i].object) !== 'undefined') {
    					loaded++;
    					continue;
    				}
    			} else {
    				script = scripts[i];
    			}
    			obj.loadJavaScript(script, function(){ if (++loaded == scripts.length && typeof callback === 'function') callback(); });
    			haveLoadedScript = true;
    		}
    		if (!haveLoadedScript && typeof callback === "function")
    			callback();
    		return haveLoadedScript;
    	},
    	loadJavaScript: function(url, callback){
    		//Bradly SHARPE - Wrapper to load javascripts into head element with optional callback
    		var head = document.getElementsByTagName('head')[0];
    		if (head){
    			var script = document.createElement('script');
    			script.type = 'text/javascript';
    			script.src = url;
    			//Bradly SHARPE - If callback is passed, set up script to call callback when loaded
    			if (typeof callback == "function"){
    				obj.addArrayMethods();
    				script.onreadystatechange = function(e) {
    					if (!e) e = window.event;
    					var el = e.target || e.srcElement;
    					if (el && ['loaded', 'complete'].contains(el.readyState)) {
    						callback();
    					}
    				};
    				//Bradly SHARPE - Add default OnLoad event
    				script.onload = callback;
    			}
    			head.appendChild(script);
    		}
    	},
    }
    
Leave a Reply
Not required

CODE: Please escape code and wrap in <pre><code>, doing so will automatically syntax highlight