Poudro's blog
CTO / Data Scientist / Problem Solver - Consultant

Asynchronous and dynamic javascript loading

Asynchronous and dynamic javascript loading - poudro.com développeur web freelance


Or how to make all your javascript asynchronous and dynamic without having to rely on a third party loader or readyStateChange.

By now, almost everyone has heard of async javascript and how it is supposed to help with page load times. Although, I’ve seen lots of articles on how to do this with third party libraries or directly through the onreadystatechange technique as discussed here. These all rely on at least one classic script call with src=file syntax, or at best in-lining the call function.

That’s all and well and I often rely on these methods, but I wondered if it were possible to have just about everything load async while having as little as possible inline to keep page size and absolute load times down.

For this, I was inspired by Google analytics’ and Piwik’s asynchronous tracking methods.

What these do is create, inline, a javascript Array object onto which you ‘push’ the parameters of your tracking before actually loading the script file. Once the script has loaded, asynchronously, it reads the array and performs its operations. What I like with this way of doing things is that there is no need for a unnecessary event-ful back-and-forth and I find that quite elegant.

As an example, you could look at the source of a number of websites around today and you would see the Google anaytics async tracking code in one form or another, often it’s default inclination which can look something like this :

<script type="text/javascript">

  var _gaq = _gaq || [];

  _gaq.push(['_setAccount', 'UA-XXXXXX-X'])
  _gaq.push(['_trackPageview']);


  (function() {
    var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
    ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
    var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
  })();

</script>



Breaking this down, the first line defines the _gaq global Array without destroying any potential previous affectations. Then it pushes two sub-arrays into it with, the first setting the account info and the second the default behavior. Finally, the third step is what I am interested in here.

Enclosed within a scoping auto-executing function, the three lines create a DOM script element, define it as async and give it a src. Then it is inserted before the first script tag that exists on the page (which we know exists since we are calling this within a script tag).

As this is done from within an inline script, it can only be executed once the page has been parsed and javascript has started to execute.

Why do I find this interesting for my use? When using frameworks like Mootools or jQuery, you need to wait for the DOM and the framework to load before you can do anything. This usually requires attaching the init function to an event triggered by the framework when it is loaded. This, in turn, only happens after the DOM (or at least part of it) is ready. It can also be that you are waiting for the onreadystatechange event discussed earlier before triggering something.

In any case, you have to wait for an event before you trigger your init. Which is fine, but hardly as necessary as it would appear by how popular it is if you are factoring the whole page.

Indeed, when an asynchronously called script has loaded through the technique I am discussing, the DOM is already ready, that means you can immediately init as soon as it is reached. Although, when using a framework, you still need to insure you are loading it prior to any usage you might have of it. To do this I simply append my code to that of the framework. And thus it also has the side effect of reducing the total number of script files to retrieve to a single file.

This can only really be used if it is not imperative that the javascript be ready before the page is loaded in itself, but I usually find it is good practice to always code with this objective. I have often found that, in any case, it is better to have something quickly loaded and displayed.

In practice, what does it look like? All pages don’t necessarily need the same code to be executed, but as I want to cache everything client-side as early as possible and limit the number of file retrievals, I want to load the whole javascript in one go early on. So, I further the inspiration from the analytics, I create an Array object onto which I push the parameters of the page and then these are read once the async source is ready.

Here’s an example adapted from one of my projects :

<script type="text/javascript">

  var gVar = gVar || [];
  (function() {
    gVar.push(["function1",['param1','param2']]);
    gVar.push(["function2",['param1','param2','param3']]);
    ...

    var d=document,g=d.createElement("script"),s=d.getElementsByTagName("script")[0];
    g.type="text/javascript";g.defer=true;g.async=true;g.src="source.js";s.parentNode.insertBefore(g,s);
  })();

</script>



In this, case, the global array is called ‘gVar’. The bulk of the javascript is contained within “source.js” and this is where gVar is interpreted to load the corresponding objects to deal the dynamic elements on the page. The interpretation could look something like this :

  for (var _el=0, gl=gVar.length; _el != gl; ++_el)
  {
    if(gVar[_el][0] == "function1")
      dealWithFunction1(gVar[_el][1]);
    if(gVar[_el][0] == "function2" && gVar[_el][1].length == 3)
      dealWithFunction2(gVar[_el][1]);
    ...
  }



Which in effect takes over the role of a global init function. This is my way of doing it, but there are plenty of other ways. If you don’t need the parameters, you could just initialize a variable to a certain value, which, once read, will trigger a certain chain within your code. Also, I’ve tried this on all relatively recent browsers.

That wraps the gist if the idea. Again, this requires complete control over the page’s code. If you don’t have that, a number of fallbacks are possible and are becoming more and more popular which can only be a good thing towards making the net a faster place… :)




12 Sep 2011