JavaScript

Loading jQuery asynchronously

The recommended way to load jQuery is with a script tag. You place it on the bottom of your page and make sure every script that depends on it are loaded after. However, what happens when you want to load all your scripts Asynchronously. No script can depend on another.

If your own script loads before jQuery it won't run. If you are using the document ready event, it will simply throw an error.

Uncaught ReferenceError: $ is not defined(…)

The problem is, there is no guarantee on the order in which the browser will download the scripts. We have to make sure our code that depends on jQuery is only ran once the library is present.

In a previous post, I talked before about how we can call functions that are not defined and still get away with it. The trick is to use asynchronous functions. We will use an array to store our functions to call, and add a snippet of code at the end of our jQuery file to do the trick.

var async = async || [];
async.push(["ready",function (){
    // the document is ready
    $("#overlay").show("fast");

    // more jQuery dependent code
    ...
}]);

This code can run anytime, even if jQuery is still not present on the page. All it does is store your function in an array with with meta data (ready), that can be accessed later.

jQuery can be loaded on top or the bottom of the page, it doesn't matter really.

<script>
(function () {
    var script = document.createElement(s); 
    script.type = 'text/javascript'; 
    script.async = true;
    script.src = 'jquery.js';
    var s = document.getElementsByTagName("script")[0];
    s.parentNode.insertBefore(script, s);
})();
</script>

Method 1: Modify the jQuery file

In order to run our code, we have to make sure jQuery checks our async variable for the list of functions to run. We can add this snippet of code at the end of jquery.js

// Jquery code 
....
// end of code 
var async = async || [];
while(async.length) { // there is some syncing to be done
    var obj = async.shift();
    if (obj[0] =="ready") {
        $(obj[1]);
    }else if (obj[0] =="load"){
        $(window).load(obj[1]);
    }
}

The code is self explanatory. The async variable is looped through and depending on what the first parameter is, the functions are loaded when the document is loaded or ready.

But this will only run for the functions defined before jquery is loaded. What about the rest? Well, we can modify the async variable to be an object with a method .push() to continue receiving functions.

So right after the previous script, we can add this:

async = {
    push: function(param){
        if (param[0] =="ready") {
            $(param[1]);
        }else if (param[0] =="load"){
            $(window).load(param[1]);
        }
    }
};

Now every time a user calls the async.push() method, it will call the function on our new object instead of storing it in an array.

Method 2: Detect when jQuery is loaded

It is very common for people to load jQuery from a CDN. The problem this brings is that we cannot modify the script. Instead we can detect when jQuery is loaded and run our code. We can add our own script that we control to do so.

// loaded anywhere on the page asynchronously.
(function jqIsReady() {
    if (typeof $ === "undefined"){
        setTimeout(jqIsReady,10);
        return ;
    }
    var async = async || [];
    while(async.length) { // there is some syncing to be done
        var obj = async.shift();
        if (obj[0] =="ready") {
            $(obj[1]);
        }else if (obj[0] =="load"){
            $(window).load(obj[1]);
        }
    }

    async = {
        push: function(param){
            if (param[0] =="ready") {
                $(param[1]);
            }else if (param[0] =="load"){
                $(window).load(param[1]);
            }
        }
    };
})();

The script checks if jQuery is loaded. If not, it waits 10 milliseconds and checks again. Only when it is present will it run the functions and replace the async variable.

Method 3: On script tag load

updated 2016-04-18

Somehow I did not think of this method earlier, but it is possible to detect when a script tag content is loaded. We just have to make sure it is cross browser compatible. We can use the same async variable to store functions and run them only when jQuery is ready.

<script>
var async = async || [];
(function () {
    var done = false;
    var script = document.createElement("script"),
    head = document.getElementsByTagName("head")[0] || document.documentElement;
    script.src = 'jquery.js';
    script.type = 'text/javascript'; 
    script.async = true;
    script.onload = script.onreadystatechange = function() {
        if (!done && (!this.readyState || this.readyState === "loaded" || this.readyState === "complete")) {
            done = true;
            // Process async variable
            while(async.length) { // there is some syncing to be done
                var obj = async.shift();
                if (obj[0] =="ready") {
                    $(obj[1]);
                }else if (obj[0] =="load"){
                    $(window).load(obj[1]);
                }
            }
            async = {
                push: function(param){
                    if (param[0] =="ready") {
                        $(param[1]);
                    }else if (param[0] =="load"){
                        $(window).load(param[1]);
                    }
                }
            };
            // End of processing
            script.onload = script.onreadystatechange = null;
            if (head && script.parentNode) {
                head.removeChild(script);
            }
        }
    };
head.insertBefore(script, head.firstChild);
})();
</script>

With this method guaranties that jQuery is on the page before any of the code is executed.


Now you can write code that depends on jQuery without worrying if the library is loaded on the page yet. The advantage is that you can load all your scripts asynchronously and none of them will block the rendering of the page.


Comments

Henry Reith :

Thank you so much for this. I have just run a Google Pagespeed test on my site and its pretty much perfect apart from on Mobile I score red for blocking scripts, that Jquery is my main script.

So I am going to have to try this and see if i can get it working with Wordpress.

Ibrahim :

Hi Henry,

I used to have jQuery running on this website too and Google Pagespeed marked it as red. Now all my scripts are async. One thing that I was having a hard time with was the CSS, because those too block the page. I have my CSS loading inline, which is tricky, but if you want to know how here is a how I did it.

Huyen Vu :

I followed yours tips but have an error:

"Uncaught ReferenceError: async is not defined" at line: "async = async || [];".

Please help me. Thanks.

Ibrahim :

Hi Huyen Vu. Thank you for finding this error.

I updated the code to work now. What was missing was the keyword var. So here is how it is supposed to work:

var async = async || [];
while(async.length) { // there is some syncing to be done
    var obj = async.shift();
    if (obj[0] =="ready") {
        $(obj[1]);
    }else if (obj[0] =="load"){
        $(window).load(obj[1]);
    }
}

Cheers!

Michael :

Ok, new to this myself so apologies if this is a stupid question :) But when using the method two, I suppose I have to run that function jqIsReady somewhere?

I mean, do I have to launch it: jqIsReady(); or edit my jquery somehow?

Or do I just pretty much copy paste it as it is, load jquery async and it all works?

Ibrahim :

Hi Michael,

All you have to do is copy the script. If you take a look at the function jqIsReady(), it is a self invoking function. I only gave it a name so we can call it over and over unlti jquery is loaded. Check line 4 and you will see this:

setTimeout(jqIsReady,10);

So to test it, copy and paste the code, and add a console.log('test') after the condition to see when jquery is loaded.

I hope this makes sense :)

Michael :

Ok, thank you! I'll try that out. Made sense indeed :)

teo :

Hi, I wonder how could I use your jqIsReady function to load jQuery code only after jQuery is loaded, something like:

jqIsReady(function(){
   //jQuery code to be executed after jQuery is loaded
});

(I get Uncaught ReferenceError: jqIsReady is not defined)

Thank you!

Ibrahim :

Hi teo

if you use the code I wrote on this post, you can call functions when jquery is load. However, not with the function jqIsReady.

Here is how you can do it instead:

var async = async || [];
async.push(["ready",function (){
   //jQuery code to be executed after jQuery is loaded
}]);

But of course, you can always modify my code to make it work your way. In that case you would have to define the function jqIsReady() on top of the page, and make sure you add all functions called in an array if jquery is not present, and use the setTimeout option or onload function to trigger all functions. Soon I will update this post with a method 3 for checking when jquery is loaded.

Don't forget to check back in a few days.

PS: use 4 spaces before your code for markdown :)

:) I am code

Moni :

Hello, i try this and it work form me on Chrome(OSX). I use Method 2, but its not work on Mozilla(OSX and Win) and IE. Any suggestions?

Regards! :)

Ibrahim :

Hi Moni,

Can you tell me what didn't work? Was there any error? It always help to add console.log to your code to make sure it is running.

Mahesh Vedurupaka :

Hi, none of the methods are working for me. i tried all, but still getting $ is not defined and jquery is not defined errors in console log. please tell me where i am missing.

Ibrahim :

One of the important questions is "How are you calling jQuery?"

Try to make use of the third method (don't forget to update the link to jQuery), then call jQuery this way:

var async = async || [];
async.push(["ready",function (){
   alert("Hello jQuery");
   // note that $ is available here.
}]);

Cyril :

Hello,

I tested method 3 but it didn't work: my plugins files were loaded but not executed. Then I realized the async var is defined as a local (not global) in the in line script. When placed outside the function, it worked like a charm.

here is the code:

<script>
var async = async || [];
(function () {
  var done = false;
  var script = document.createElement("script"),
  head = document.getElementsByTagName("head")[0] || document.documentElement;
  script.src = '/js/2016/jquery-3.1.1-min.js';
  script.type = 'text/javascript'; 
  script.async = true;
  script.onload = script.onreadystatechange = function() {
      if (!done && (!this.readyState || this.readyState === "loaded" || this.readyState === "complete")) {
          done = true;
          console.log('jquery main script is ready');
          // Process async variable
          //var async = async || [];
          while(async.length) { // there is some syncing to be done
              var obj = async.shift();
              if (obj[0] =="ready") {
                  $(obj[1]);
              }else if (obj[0] =="load"){
                  $(window).load(obj[1]);
              }
          }
          async = {
              push: function(param){
                  if (param[0] =="ready") {
                      $(param[1]);
                  }else if (param[0] =="load"){
                      $(window).load(param[1]);
                  }
              }
          };
          // End of processing
          script.onload = script.onreadystatechange = null;
          if (head && script.parentNode) {
              head.removeChild(script);
          }
      }
  };
head.insertBefore(script, head.firstChild);
})();
</script>

Thank you

Ibrahim :

Thank you so much Cyrill. Just a week ago I received a similar comment from @ultrahdart on twitter and I had helped him to fix it. But then I forgot to update the code.

Thanks again, I will update.

daniel :

Of course it doesnt work for me.

I see in the console: "jquery main script is ready" I put the last posted code with my librarie script.src correct before the boy tag.

After that i load other scripts. All script tags are with the async attribute. like:

<script async defer src="<?=HTML_ROOT?>js/jquery-ui.js"></script>               
<script async defer type="text/javascript" src="<?=HTML_ROOT?>js/jquery.validate.min.js"></script>
<script async defer type="text/javascript" src="<?=HTML_ROOT?>js/jquery.mmenu.min.all.js">  </script> 

And as expected i get:

"jquery-ui.js:1 Uncaught ReferenceError: jQuery is not defined"

Also no scripts tags are created in the header

Ibrahim :

H Daniel.

You are adding those scripts directly on the page but there is no guarantee that jQuery is present at that time.

The solution is to wait until the main library is loaded to add them, which is the point of this tutorial.

To do so, we have to make use of our async object:

var async = async || [];
async.push(["ready",function (){
   // helper function to load scripts.
   function loadScript(url){
      var script = document.createElement("script"); 
      script.type = 'text/javascript'; 
      script.async = true;
      script.src = url;
      var s = document.getElementsByTagName("script")[0];
      s.parentNode.insertBefore(script, s);
   }

   // add scripts to the page.
   loadScript("<?=HTML_ROOT?>js/jquery-ui.js");
   loadScript("<?=HTML_ROOT?>js/jquery.validate.min.js");
   loadScript("<?=HTML_ROOT?>js/jquery.mmenu.min.all.js");
}]);

I hope this works for you.

Daniel :

Thanks Ibrahim,

I thought everything you declared below this function would load it after the jquery is loaded.

So i guess the code would be now:

<script>
var async = async || [];
(function () {
  var done = false;
  var script = document.createElement("script"),
  head = document.getElementsByTagName("head")[0] || document.documentElement;
  script.src = '/js/2016/jquery-3.1.1-min.js';
  script.type = 'text/javascript'; 
  script.async = true;
  script.onload = script.onreadystatechange = function() {
      if (!done && (!this.readyState || this.readyState === "loaded" || this.readyState === "complete")) {
          done = true;
          console.log('jquery main script is ready');
          // Process async variable
          //var async = async || [];
          while(async.length) { // there is some syncing to be done
              var obj = async.shift();
              if (obj[0] =="ready") {
                  $(obj[1]);
              }else if (obj[0] =="load"){
                  $(window).load(obj[1]);
              }
          }
          async = {
              push: function(param){
                  if (param[0] =="ready") {
                      $(param[1]);
                  }else if (param[0] =="load"){
                      $(window).load(param[1]);
                  }
              }
          };
          // End of processing
          script.onload = script.onreadystatechange = null;
          if (head && script.parentNode) {
              head.removeChild(script);
          }
      }
  };
head.insertBefore(script, head.firstChild);
})();

/// 

   // helper function to load scripts.
   function loadScript(url){
      var script = document.createElement(s); 
      script.type = 'text/javascript'; 
      script.async = true;
      script.src = url;
      var s = document.getElementsByTagName("script")[0];
      s.parentNode.insertBefore(script, s);
   }

   // add scripts to the page.
   loadScript("<?=HTML_ROOT?>js/jquery-ui.js");
   loadScript("<?=HTML_ROOT?>js/jquery.validate.min.js");
   loadScript("<?=HTML_ROOT?>js/jquery.mmenu.min.all.js");

</script>

Another question is it possible to use inline jquery after it has been loaded?

What about a php page where there is jquery and PHP mixed?

really too bad but nothing is working with this script. no errors as well.

I got a lot of inline jquery that i put all together. No click eventsm mouseover etc from jquery that I have added in headerjs.php are running.

Thanks

Daniel

Ibrahim :

Hi Daniel. Looks like I had a small typo on my loadScript() function. Here is the updated version.

function loadScript(url){
    var script = document.createElement("script");// <-- here 
    script.type = 'text/javascript'; 
    script.async = true;
    script.src = url;
    var s = document.getElementsByTagName("script")[0];
    s.parentNode.insertBefore(script, s);
}

To answer your other question, yes you can make inline jQuery, as long as you use it with the async object.

async.push(["ready",function (){
   // the document is ready
   $("#overlay").show("fast");

   // more inline
   ...
}]);

Remember making your jQuery Async requires that you update your entire code. Everywhere you used to call jQuery directly has to be updated. With the new update, your scripts will load. but remember to pay attention to the dependency of your code.

Daniel :

Thanks but an important question is the load order. Do you know if this way guaranthees that Jquery libs and inline code can be loaded in a specific order? This is to due to dependencies.

Ibrahim :

After the main jQuery script is loaded, you have access to the jQuery object. At that point, you can make use of the jQuery .getScript() method to guarantee the order of your scripts with Promises.

For example.

async.push(["ready",function (){
  $.when(
    $.getScript( "/mypath/myscript1.js" ),
    $.getScript( "/mypath/myscript2.js" ),
    $.getScript( "/mypath/myscript3.js" ),
    $.Deferred(function( deferred ){
        $( deferred.resolve );
    })
  ).done(function(){
    //place your code here, the scripts are all loaded
  });

}]);

This will guarantee your dependencies.

Adam :

Great work Ibrahim, and thanks for keeping the comments active with detailed responses. Just one thing - is there a way to trigger a callback after a module has loaded using loadScript()?

Ibrahim :

Hi Adam,

You sure can add an extra parameter to loadScript to trigger a call back function. We can use the same method as method 3 by adding our call back on onload.

Simplified version:

function loadScript(url,callback){
    var script = document.createElement("script");// <-- here 
    script.type = 'text/javascript'; 
    script.async = true;
    script.src = url;
    // here we do callback
    if (callback){
        script.onload = callback;
    }
    var s = document.getElementsByTagName("script")[0];
    s.parentNode.insertBefore(script, s);
}

I hope this helps.

Thanks for the feedback =)

Jonathan B :

Just wanted to chime in and say thank you for the script to async in js files. I had a project that required all js files to be loaded async. Unfortunately this causes an issue with foundation being chained at document ready. The work around was as follows.

async.push(["ready", function() {
     loadScript('js/build/production.js', function() {
       loadScript('js/foundation-calls.js');
    });
}]);

All of the foundation components and custom js are in the production.js file and the initialization of foundation and accompanying parts is included in the foundation calls file.

Ibrahim :

Thanks Jonathan, and great job consolidating. I think this is the best way to set it up.

Let's hear your thoughts

For my eyes only