Pressure is nothing more than the shadow of great opportunity. - Michael Johnson

Dynamically loading JS libraries and detecting when they're loaded

7 years, 4 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

  1. function loadScript(sScriptSrc) {
  2. var oHead = document.getElementById('head')[0];
  3. var oScript = document.createElement('script');
  4. oScript.type = 'text/javascript';
  5. oScript.src = sScriptSrc;
  6. oHead.appendChild(oScript);
  7. }

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.

  1. 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.

  1. oScript.onreadystatechange = function() {
  2. if (this.readyState == 'complete') {
  3. callback();
  4. }
  5. }

Combining the parts gives us:

  1. function loadScript(sScriptSrc, oCallback) {
  2. var oHead = document.getElementById('head')[0];
  3. var oScript = document.createElement('script');
  4. oScript.type = 'text/javascript';
  5. oScript.src = sScriptSrc;
  6. // most browsers
  7. oScript.onload = oCallback;
  8. // IE 6 & 7
  9. oScript.onreadystatechange = function() {
  10. if (this.readyState == 'complete') {
  11. oCallback();
  12. }
  13. }
  14. oHead.appendChild(oScript);
  15. }

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.

  1. function onFunctionAvailable(sMethod, oCallback, oObject, bScope) {
  2. if (typeof(eval(sMethod)) === 'function') {
  3. bScope ? oCallback.call(oObject) : oCallback(oObject);
  4. } else {
  5. setTimeout(function () {
  6. onFunctionAvailable(sMethod, oCallback, oObject, bScope);
  7. }), 50
  8. }
  9. }

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.

Further Reading

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

    if( typeof(eval(funcName)) === 'function' ) { eval(sMethod); } else { setTimeout( function () { onFunctionAvailable(sMethod ); } ), 1000 }

    }

    Jordan Catalano - 2nd November 2007 #

  • ejeliot,

    you saved me lots of heartache. much appreciated.

    danny - 12th November 2007 #

  • An example will be appeciated too :)

    NASDAK - 2nd December 2007 #

  • 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] )

    Ward - 2nd December 2007 #

  • Hi, have you tried to detect if an error ocurred while loading the script?

    I wrote the following script, works in Firefox but not in IE:

    var resourceElement = document.createElement('script'); [... etc etc ...]

    resourceElement.onerror = function(message) { alert(message); };

    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. :)

    Thanks,

    LK

    lufublico - 19th December 2007 #

  • Thanks for the sum up. Very good information in here.

    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.

    Till - 8th January 2008 #

  • it worked very well I think this: var oHead = document.getElementById('head')[0];

    to be replaced as var oHead = document.getElementsByTagName('head')[0];

    anyway fine.

    Web Tips in Javascript

    Raja - 13th June 2008 #

  • In IE 7:

    this.readyState

    was 'loading' subsequently 'loaded'.

    I changed:

    if (this.readyState == 'complete')

    to:

    if (this.readyState == 'loaded' || this.readyState == 'complete')

    Maarten Maarten

    Maarten - 26th June 2008 #

  • 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.

    Hunan - 23rd November 2008 #

  • If you want to test whether one particular function ("Foo()") has been loaded, use this:

    function RunFooWhenReady(param) { if(window.Foo==undefined) { setTimeout(function() RunFooWhenReady(param);},50); } else { Foo(param); } }

    Nick - 26th March 2010 #

  • I suspect the call to 'oCallBack' inside the closure may lead to issues in case of successive calls to the import function:

    oScript.onreadystatechange = function() { if (this.readyState == 'complete') oCallback(); //

    Louis LC - 19th May 2010 #

  • Hi Ed,

    wondering if:

    typeof eval(sMethod)

    will raise an exception when sMethod is not defined.

    Salman - 6th July 2010 #

  • Thanks a Lot.... from last 4 days i am trying to do something...not able to do... but with the help of this code, finally..finally :) .... Thanks a lot... i save the code for my future reference....

    Abhijit - 14th September 2010 #

  • 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.

    Thanks

    Den - 21st September 2010 #

  • The example was useful - except that it doesn't work: would you mind replacing getElementById with getElementsByTagName in your code example, as Till suggested above?

    Thanks! *t

    tpo - 22nd May 2011 #

  • Wow thank you, works like a charm :) I used this method to load the +1 button asynchronously, I needed to know when the Google JS was loaded. You solved my problem!

    Hautclocq Gabriel - 14th July 2011 #

  • Nice article! In "Detecting script load with timeouts" section the example code is mistyped, the set timeouts delay is outside the function call... the on line 7... The correct: setTimeout(function () { onFunctionAvailable(sMethod, oCallback, oObject, bScope); }, 50);

    roki - 2nd May 2012 #

  • alhamdulillah.. thanks a lot.. i have try to solve problem load js file from javascript dynamically.. almost 3 hours and there is no good answer.. this time... was success.. thank u..

    Pandu - 11th June 2012 #

  • hey, this works great but there is a little error in your onFunctionAvailable function, it should be:

    setTimeout(function () { onFunctionAvailable(sMethod, oCallback, oObject, bScope); }, 50)

    The parenthesis at "50" got a bit mixed up

    Philipp Hoffmann - 31st August 2012 #

  • 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.

    function loadScript(src, async, elem, callback) { /// Loads a script. elem = elem || _document.createElement('script'); elem.async = elem.defer = async; elem.src = src;

    if (callback) { if (elem.readyState) { // IE elem.onreadystatechange = function () { if (elem.readyState == "loaded" || elem.readyState == "complete") { elem.onreadystatechange = null; callback(); } }; } else { // Good browsers elem.onload = function () { callback(); } } }

    insertScript(elem); }

    function insertScript(e) { /// Inserts a script into the document. var ib = _document.getElementsByTagName('script')[0]; ib.parentNode.insertBefore(e, ib); }

    Levi - 16th October 2013 #

Help make this post better

Notes: Standard BBCode for links, bold, italic and code are supported. rel="nofollow" is added to all links. Your email address will never be displayed on the site.

Back to index