Async Defer — JavaScript Loading Strategies

Image for post
Image for post

JavaScript is an essential part of any basic/modern web application and the strategies we decide to load it directly impacts the performance to a greater extent.

In this article, we will try understanding the important differences between each approach, pros & cons along with the performance implications and ways to optimize page interaction/load time.

For demonstration purpose — I am creating a website consisting of the following external dependencies. Just pay close attention to the respective file sizes, as file download time is directly proportional to that.

  1. HTML Page ~1 MB
    It contains the actual markup/content and will provide some placeholder [id=’output’] to show some dynamic output from JavaScript.
  2. Image — image1.png ~ 5 MB
  3. JavaScript — file1.js ~ 3 MB
    It is core/main javascript file and dependent on the parsed HTML to show some dynamic content or mount react/angular/view component on the page.
  4. JavaScript — file2.js ~ 460B
    Small, Independent javascript file which interacts with dom.
  5. JavaScript — file3.js ~ 1.5 MB
    It is secondary js file and dependent on file1.js to execute some lower priority code those are not required immediately for page rendering & user interactions like showing social sharing icons, integrating with comment/chatbot assistance, running some analytics tasks etc.

Approach — 1 [scripts in head section]

In the first case, we will load all 3 scripts tag in the head section of our HTML.

Image for post
Image for post
1. HTML code

Below is the screenshot of the chrome Network tab analysis of the page once ready for user interaction.

Image for post
Image for post

Pros:

  1. Code execution sequence of different JS files will be preserved in the order files were included in HTML. In current example, even though file2 & file3 were downloaded before file1, execution order will be correctly taken care.
Image for post
Image for post
1. Console window

So calling an user’s object method user.greet() inside file3.js will work perfectly fine and outputs “Welcome John Doe” message on console.

Cons:

In this scenario — HTML parsing will be suspended until all the 3 scripts in head section are downloaded, parsed and executed. So,

  1. A plain white screen will be shown to the user even if the HTML file has been already downloaded [But not parsed]. This is definitely not a good user experience.
Image for post
Image for post
1. Main Window — Blank

2. NONE of the above scripts will be able to access/manipulate the HTML page as DOM is not yet ready.

// Below code will throw an Error
var output = document.getElementById('output');
output.innerText = 'Welcome message from JS - File 1';

One possible solution to handle this issue it to listen for DOMContentLoaded event and then execute your code after that.

The DOMContentLoaded event fires when the initial HTML document has been completely loaded and parsed, without waiting for stylesheets, images, and subframes to finish loading.

document.addEventListener('DOMContentLoaded', function(event) {
// Put your DOM manipulation logic here
});

📌 Demo

Approach — 2 [scripts at the end]

To overcome the 2 issues faced in the first approach, let’s load all 3 scripts at the bottom of the body tag.

Image for post
Image for post
2. HTML code

Pros:

  1. As HTML is parsed before downloading the scripts, so user will be able to see the actual content immediately instead of waiting for scripts.
Image for post
Image for post

2. As all the scripts are executed after parsing the HTML, so all of them can access the DOM for any manipulation.

Image for post
Image for post

3. Sequence of script execution is preserved.

Cons:

No performance gain as such.

📌 Demo

Approach — 3 [using Async attribute]

<script src=”/javasctipt-file-path” asyn></script>

HTML5 introduced the async script attribute which helps in downloading the respective script files in-parallel on a different thread without impacting the HTML parsing.

However, the corresponding script will be parsed and executed as soon as it completes downloading regardless of whether or not HTML parsing has completed and will have the reference to DOM element until that particular point.

Image for post
Image for post
Image for post
Image for post

Here you can clearly see the file2.js has been downloaded before HTML file. However, while the browser is downloading file2, it didn’t pause the HTML parsing and because of that, by the time it was executed — it had reference to html placeholder to inject the dynamic content.

<span id="output2">Empty</span>

Pros:

  1. As scripts will be downloaded on a different thread, the HTML parsing will not be paused and user will be able to see immediate content instead of White blank screen.
  2. Major performance boost i.e. DOMContentLoaded time decreased from 47.68 sec to just 21.12 sec and translates to ~55% gain.

Cons:

  1. Sequence of JS execution is NOT preserved. It is executed in the respective download order rather than the included script sequence inside HTML.
  2. Browser support — Not supported on the older web browsers i.e. IE 9 and below.
  3. What if the JS is downloaded before DOM element is available?
    Refer the below scenario where we have approx ~12k lines of HTML before the actual placeholder where dynamic content was supposed to be injected by file2. In this case, file2.js was downloaded and executed before respective DOM element was available and thrown error.
Image for post
Image for post

Note: Putting the scripts with async attribute at the bottom of body section will be of NO use and equivalent to Approach-2.

📌 Demo

Approach — 4 [using Defer attribute]

Defer script attributes it same as Async attribute except the respective scripts are parsed and execute ONLY after HTML parsing has been completed.

Image for post
Image for post

One very important point to be considered here is that Chrome doesn’t support defer yet and showing NO impact on the DOMContentLoaded duration. However, it is executing the scripts at the end of HTML parsing.

Image for post
Image for post

Pros:

  1. Sequence of script import is preserved. So, file3.js executed only after file1 has completed downloading and execution even though file3 was downloaded before.
  2. Browser support — It has a better browser support compared to Async attribute i.e. partially supported in IE v6–9
  3. Scripts can access the DOM as it is executed only after parsing the complete HTML.

Cons:

Initially I thought that defer would be better choice then Async but later found out that Chrome doesn’t support it yet [Version 71.0.3578.98] and having NO impact on the DOMContentLoaded duration.

Image for post
Image for post

📌 Demo

Here is more on the discussion : https://stackoverflow.com/questions/3952009/defer-attribute-chrome

However, it is working as expected on Firefox with significant improvement in the performance 🎯

Image for post
Image for post

🏁 Conclusion:

  1. Prefer keeping the scripts tags in the head section with async attribute for third party libraries which are in-depended like Google Analytics, Google reCAPTCHA or anything else which doesn’t need DOM access as the respective scripts are downloaded in parallel but executed immediately.
  2. Use defer for all the other scripts loaded in head section as these will also be downloaded in parallel but executed only after HTML parsing has completed and DOM is ready for access/manipulation.
  3. You can also utilize combining DOMContentLoaded listener inside async scripts to execute the functionality later.

Please put your opinions and findings in the comment section and I will be glad to discuss further on that.

If you like this post and it was helpful, please click the clap 👏 button multiple times to show the support, thank you.

Written by

JavaScript Enthusiast

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store