Async Defer — JavaScript Loading Strategies

Ravi Roshan
Jan 3, 2019 · 6 min read

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.

Now it’s time to analyze the various approaches 🕵🏻

Approach — 1 [scripts in head section]

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

1. HTML code

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

  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.
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.

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.
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

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.

2. HTML code
  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.

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

3. Sequence of script execution is preserved.

No performance gain as such.

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.

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>
  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.
  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.

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

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.

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.

  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.

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.

Here is more on the discussion :

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

🏁 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.

Ravi Roshan

Written by

JavaScript Enthusiast

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade