Efficiently load JavaScript with defer and async

0 Shares
0
0
0
0
0
0
0

With HTML5, we get two new boolean attributes for the <script> tag: async and deferAsync allows execution of scripts asynchronously and defer allows execution only after the whole document has been parsed.

Async vs Defer

With async, the file gets downloaded asynchronously and then executed as soon as it’s downloaded.

With defer, the file gets downloaded asynchronously but executed only when the document parsing is completed. With defer, scripts will execute in the same order as they are called. This makes defer the attribute of choice when a script depends on another script. For example, if you’re using jQuery as well as other scripts that depend on it, you’d use defer on them (jQuery included), making sure to call jQuery before the dependent scripts.

A good strategy is to use async when possible, and then defer when async isn’t an option.

Five minutes to understand async and defer

When we want to insert a script into a web page, the standard way is to use the script tag(i.e. <script>). The first impression that comes to people’s mind about the script tag is — BLOCKING. The book High-Performance Web Sites Rule 6 suggests putting scripts at the bottom of the html body. The article will examine how putting scripts at varying positions affects performance and how async and defer attributes work for script tag.

First thing first, when a script is referenced in an HTML page, the browser does two things for you:

Retrieve/Load the script content, this is NON-BLOCKING!
Run the script content, this is BLOCKING!

Assume we have two scripts on the page:

//script1.js
let t1 = +new Date;
console.log('script1 is running at', t1);
console.log('script1 element', document.getElementById('load-experiment'));
while((+new Date) - t1 < 1000) {
    // delay 1 seconds
}
console.log('script1 finishes running at', +new Date);
//script2.js
let t2 = +new Date;
console.log('script2 is running at', t2);
console.log('script2 element', document.getElementById('load-experiment'));
while((+new Date) - t2 < 2000) {
    // delay 2 seconds
}
console.log('script2 finishes running at', +new Date);

Put script tags in the head

<!--all-in-head.html-->
<html>
    <head>
        <title> test js tag async and defer attributes</title>
        <script src='./script1.js'></script>
        <script src='./script2.js'></script>
    </head>
    <body>
        <h1 id='load-experiment'> hello world </h1>
    </body>
</html>
The console output:
script1 is running at 1496747869008
script1.js:4 script1 element null
script1.js:8 script1 finishes running at 1496747870008
script2.js:2 script2 is running at 1496747870009
script2.js:4 script2 element null
script2.js:8 script2 finishes running at 1496747872009

Conclusion:

  • When we open the html in the browser, we can notice the delay of page load. The page goes blank before it renders correctly. This is due to the fact that the running of the two scripts blocks the DOM rendering.
  • When scripts are running, they are not able to fetch the DOM element (i.e. document.getElementById(‘load-experiment’) being null). This is because scripts are run before DOM is rendered.
  • Scripts themselves are blocking each other. They are run in the order specified in the html. Script2 is run after script1 finishes running.

Put script tags at the bottom of the body

<!--all-in-body.html-->
<html>
    <head>
        <title> test js tag async and defer attributes</title>
    </head>
    <body>
        <h1 id='load-experiment'> hello world </h1>
        <script src='./script1.js'></script>
        <script src='./script2.js'></script>
    </body>
</html>
The console output:
script1 is running at 1496751597679
script1.js:4 script1 element <h1 id=​"load-experiment">​ hello world ​</h1>​
script1.js:8 script1 finishes running at 1496751598679
script2.js:2 script2 is running at 1496751598680
script2.js:4 script2 element <h1 id=​"load-experiment">​ hello world ​</h1>​
script2.js:8 script2 finishes running at 1496751600680

Conclusion:

  • No page delay and blank page when opening the page in browser, as scripts are put/run after the DOM is ready.
  • Scripts can correctly fetch DOM elements.
  • Scripts themselves are blocking each other, as the same as the first example.

Conclusion

The general rule to import script is:

<html>
    <head>
        <!--headScripts.js is the script that has to be loaded and run before DOM is ready-->
        <script src="headScripts.js"></scripts>
        <!--bodyScripts.js loads first and runs after DOM is ready-->
        <script defer src="bodyScripts.js"></script>
    </head>
    <body>
        <!--body content-->
        <h1 id='load-experiment'> hello world </h1>
        <!--independent scripts,nice-to-have -->
        <script async src="forumWidget.js"></script>
        <script async src="chatWidget.js"></script>
    </body>
</html>

Code Sample: https://github.com/n0ruSh/the-art-of-reading/tree/master/javascript/Async%20Javascript/defer-async

Reference