Hi, I'm Andrew Del Prete

Learnperf.com is a series of posts on web performance geared towards beginners.

App Performance Low Hanging Fruit — Part 6: Async / Defer

Back in Part 3 we talked about the CRP (Critcal Rendering Path) and how we should be aware of and limit render blocking resources. In this post we’re going to look at the async and defer keywords and how they can help speed up the path to render.

Script Blockage

When the browser loads a site and encounters a <script> tag during the HTML Parsing phase, the browser will pause the HTML Parser to download AND execute the script in the main thread. This means that the subquential HTML and any script tags below will not be parsed until the first one in the path is finished. This process will continue until all render blocking scripts are downloaded and executed.

This is all well and good if you intend for your script to be downloaded and immediately executed, but many times the scripts we load on a page are not essential to the initial rendering of that page. This is what I mean when I say above that “we should be aware of and limit render blocking resources”. It’s all about intention.

Script Loading Methods

In the examples below we’re going to look at a few variations of loading one <script> tag. Many of us have heard that loading a <script> at the bottom just before </body> is a best practice, why is that? How does async and defer differ?

These examples read left to right and demonstrate the process your browser takes when encountering a <script> tag.

= - Parsing HTML
* - Pause Parsing HTML
d - Downloading the Script
x - Executing the Script

Normal (without async / defer)

<head>
  <script src="a.js"></script>
</head>
<body>...</body>

======**********====>Render
      ddddd
           xxxxx

  1. Parses HTML
  2. Encounters script
  3. Pauses HTML Parser
  4. Begins downloading script
  5. Immediately executes script
  6. Continues Parsing HTML
  7. Render

When we add a <script> tag to the <head> or somewhere in <body> the parser is paused until the script has been downloaded and executed. This may be necessary in some scenerios but most of the time we should put our scripts just before </body>.

Loading scripts directly before </body>

...
<body>
  ...
  <script src="b.js"></script>
</body>

===============*****>Render
          ddddd
               xxxxx

  1. Parses all HTML because script is at the bottom
  2. Encounters script
  3. Pauses HTML Parser
  4. Begins downloading script
  5. Immediately executes script
  6. Render

Most of us have heard it’s best to put our <script> tags at the bottom just before </body> and for good reason. The more we can defer the downloading and execution of a script the more the HTML Parser can get done. This allows the rest of the site to parse and download necessary images, CSS, etc… THEN download and execute scripts.

Async

<head>
  <script async src="c.js"></script>
</head>

===========*****====>Render
      ddddd
           xxxxx

  1. Parses HTML
  2. Encounters async script
  3. Continues Parsing HTML
  4. Downloads script while still parsing HTML
  5. Immediately executes script when finished downloading
  6. Continues Parsing HTML
  7. Render

Many scripts aren’t necessary for the rendering of a page. External scripts like Google Analytics, Ad scripts, etc… shouldn’t keep a page from rendering. Since scripts marked async will download and execute at any point during the HTML Parsing phase, they should be self contained and not rely on other scripts to execute.

Defer

<head>
  <script defer src="d.js"></script>
</head>

===============*****>Render
      ddddd
               xxxxx

  1. Parses HTML
  2. Encounters defer script
  3. Continues Parsing HTML
  4. Downloads script while still parsing HTML
  5. Finishes downloading but waits for HTML to finish parsing before executing
  6. Executes deferred scripts in the order they were found on domInteractive
  7. Render

Scripts marked defer will begin downloading at the time the HTML Parser sees them but won’t execute until the DOM is completely parsed. Unlike async, these scripts will execute in the order they were found in the HTML. IOW, if one script depends on another script, you’re guaranteed these will execute in the proper order.

You might be asking yourself, what’s the difference between using a defer script in <head> and just loading our script before </body> tag? The answer as I understand it, not much.

Misconceptions

Many people misunderstand the use of async and defer by thinking these methods will remove scripts from the Critical Rendering Path and therefore render your page faster. This is only partially true. No matter which method you use above, the scripts must be fully downloaded and executed before the site will render. The main perf win we get from utilizing these methods is unblocking the HTML Parser and allowing the browser to continue parsing HTML and downloading resources.

If you want to load a <script> AFTER the page has been rendered, than something like $.getScript or a vanilla JS function can be used to manaully inject a <script> tag after the page has been rendered.

Resources

  1. Script Tag - async & defer
  2. Difference between async and defer attributes in script tags
  3. Defer loading Javascript

Thanks for reading,
Andrew Del Prete