JavaScript

Asynchrounous JavaScript

If you learn a JavaScript tutorial today, chances are they told you that all the scripts are loaded in the head of the document. Then as you advance in your learning, you will see that it is religiously recommended that you add your code at the bottom of the page.

The reason the code is added on top of the page, is because it is much easier to find. You look at your code and it is right there in the head of the document, very well organized.

The reason we add the code to the bottom of the page is a little less obvious. It is to give the user a better experience. You see, the browser may take some time to download all these scripts, thus slowing down the rendering of the page. Google says that the faster your page loads, the happier your users will be.

Google saidth, people doth.

So if we all agree that we load the script on the bottom of the page then there will be less confusion. But we don't all agree. Newer browsers added new script tag attributes to make our life easier. defer and async.

If the defer tag is present, the browser waits for the DOM to be ready then downloads the script file. If the async attribute is present, the browser downloads the script in parallel without blocking execution of the rest of the page.

<script type="text/javascript" src="script1.js" defer></script>
<script type="text/javascript" src="script2.js" async></script>

Unfortunately, that's for new browsers only. In order to support the old ones too, you will have to add the script dynamically to the page. If I wanted to add script.js to the page, this is how I would do it.

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

Note: We are making use of a self invoking function.

The script is asynchronously added to the page and I don't have to worry about it blocking the rest of the page from rendering.

Asynchronously loading script to the page comes with its own set of problems. For example, you cannot call functions from the script directly on the page, because you don't know if it was loaded or not. The example we used on the previous post will not work so well. It will all depend on how fast the browser loads it which is very inconsistent:

<div class="newsletter">
    <!-- Validate email and make ajax request to subscribe the user -->
</div>
<script>initializeNewsletter();</script>

Sometimes, depending on your connection speed, the browser will throw an error saying initialiazeNewsletter is not defined. But don't worry, we have a solution for that too.

Introducing Asynchronous JavaScript.

The trick is to specify a place where we want to call a function, and let our script call it whenever it is ready. This is what I use on this website. I wrote some code to add syntax highlighting to my article. Right after the article, I "pseudo-call" the function. That is defined in an external script that is loaded asynchronously. If the function is defined, it calls it right away, otherwise it waits until it is.

Let me show you the code:

// script.js : this is the file loaded asynchronously.
var script = {};
script.fn = {};
script.fn.syntaxHighlighter = function (){/* Syntax highlighter script */ console.log("syntax")};
script.fn.newsletter = function(){/* Newsletter script */console.log("news")};

var syncer = syncer || [];
while(syncer.length) { // there is some syncing to be done
    var obj = syncer.shift();
    if (script.fn[obj[0]]) {
        script.fn[obj[0]].apply(script.fn,obj[1]);// call the script function with it's additional parameters;
    }else {
        throw new Error("Function '"+obj[0]+"' does not exist.'");
    }
}

syncer = {
    push: function(param){
        if (script.fn[param[0]]) {
            script.fn[param[0]].apply(script.fn,param[1]);// call the script function with it's additional parameters;
        }else {
            throw new Error("Function '"+obj[0]+"; does not exist.'");
        }
    }
};

That was a little intense. So here is what is happening. syncer is an array that will hold the name of our functions and the parameters they take. When the script is downloaded, we check if syncer has any elements. If it does we check if any of the functions exist and we call them other wise we throw an error. Note that at the end, I modify the syncer array to be an object instead with a push method. This way we can call functions directly, since the script is already loaded.

Now on our page we can call our functions whether they exist or not.

<div class="newsletter">
    <!-- Validate email and make ajax request to subscribe the user -->
</div>
<script>
var syncer = syncer || []; // reuse it if it was defined before
syncer.push(["newsletter",["arguments",1,2,3]]);
</script>

There you go, we created an asynchronous function that is independent from whether it's definition is added to the page or not.


Comments

There are no comments added yet.

Let's hear your thoughts

For my eyes only