Dynamically loading JS libraries and detecting when they're loaded

7 years, 6 months ago

Sometimes it makes sense to dynamically load JavaScript library code only at the point at which it's required. If functionality which uses that library is used rarely then it makes little sense incur the overhead of an additional HTTP request or data transfer on page load.

We can dynamically load any JavaScript and add it to the document with the following function.

Making a dynamic request

function loadScript(sScriptSrc) {

var oHead = document.getElementById('head')[0];

var oScript = document.createElement('script');

oScript.type = 'text/javascript';

oScript.src = sScriptSrc;

oHead.appendChild(oScript);

}

This approach isn't without problems though. The request will load asynchronously which means that any JavaScript which depends on code returned by the request may continue to run before the request completes resulting in errors.

Callback event handlers

As described in far greater detail in JavaScript Madness: Dynamic Script Loading browsers provide the ability to specify a callback function to run once the script load completes. In most it's as simple as assigning a callback function to the script.onload event handler.

oScript.onload = callback;

Internet Explorer, as in so many cases, does things differently. It supports a onreadystatechange event handler to which we can assign a callback function. It's slightly more complicated than that used by other browsers as one has to check a return status before running the callback.

oScript.onreadystatechange = function() {

if (this.readyState == 'complete') {

callback();

}

}

Combining the parts gives us:

function loadScript(sScriptSrc, oCallback) {

var oHead = document.getElementById('head')[0];

var oScript = document.createElement('script');

oScript.type = 'text/javascript';

oScript.src = sScriptSrc;

// most browsers

oScript.onload = oCallback;

// IE 6 & 7

oScript.onreadystatechange = function() {

if (this.readyState == 'complete') {

oCallback();

}

}

oHead.appendChild(oScript);

}

Detecting script load with timeouts

Reading a number of sources however it seems that there is a lack of trust in the cross browser robustness of these event handlers. One could instead use timeouts to poll for the existence of required functions returned by the script request and run our callback once detected.

I wrote the following function to achieve just this recently. The function takes two main parameters, a string representing the function to poll for and a reference to the callback function to run once it's found.

function onFunctionAvailable(sMethod, oCallback, oObject, bScope) {

if (typeof(eval(sMethod)) === 'function') {

bScope ? oCallback.call(oObject) : oCallback(oObject);

} else {

setTimeout(function () {

onFunctionAvailable(sMethod, oCallback, oObject, bScope);

}), 50

}

}

Two additional parameters allow one to define the scope of "this" within the callback function.

The hardest part of this function was working out how to detect the existence of a function from a string. Fortunately there's no shortage of people I can turn to with JavaScript skills better than my own. Thanks to Christian, Stuart and Andrew for their input.

Comments

Hey Ed - I was trying to implement a version of onFunctionAvailable but having some trouble getting it to work in Safari. Perhaps my edits are causing the problem. I was a little confused on what the parameter oObject and bScope represent and removed them. Any chance you'd give an example of how to call your function? Thanks in advance, I found your blog helpful.
btw - here is my cutdown version:
---
function onFunctionAvailable(sMethod)
{
var a = sMethod.split('(');
if( a.length < 1 ) {
return;
}
var funcName = a[0];

Nice! I wasn't aware of the onload/readystatechange properties of the script object. BTW from what I've read eval is considered a security risk and you are going ot want to use square bracket syntaxt to detect that function - if( typeof( window[funcName] )

For IE, the only thing I tried is using window.onerror , but then I can't identify which script failed. Anyway it works if you just want to know if *any* of the required scripts failed, but with a really poor level of accuracy.

I'd appreciate any help or thoughts you may share. Better if you write an article about handling dynamic JS loading errors. :)

One question though: Wouldn't it be sufficient if you simply call the callback function from your (dynamically loaded) JavaScript file? This should work at least as long as you have control over that file.

Sounds like the first option you mentioned if definitely out, and the timeout with (typeof(abc) == 'function') or (typeof(abc) == 'object') is the only way to go. But, what if the function or object is only partially loaded at this point? Or do browsers load the entire script file before executing? Seems like there is still no final answer on how to do this. Thanks.

Thanks for this. I'm sort of learning by trial and error. I looked around for dynamically loading javascript and everything I found was either too complicated for me or it wouldn't work the way I wanted it to.

I had this up and running in 15 minutes. I changed it around a bit so that it gets the name of the javascript file based on the name of the html file in my right iframe and then sends the javascript to the left iframe when you click on the image map in the right iframe.

Assigning onload and onreadystatechanged as you have shown will cause the callback to be called twice (as tested in IE10). You need to cancel the event handlers if you are going to do it that way. Or you can check for readystate first and use an else condition to prevent wiring up two event handlers.