site icon

Hi, I’m Chris

I am a web developer teaching
regular people how to code

~ Welcome to my blog ~

Control Javascript loading with Async & Defer

When loading Javascript from a HTML file, we can place the link anywhere in the file, common options are the head section, or down at the bottom of the body.

The placement of this link though is important, and can have side effects depending on where you add it.

As a basic example, if we add some Javascript in the head section like this:

<head>
  <script>
    document.getElementById('title').innerText = 'New title'
  </script>
</head>

<body>
  <h1 id='title'>Old title</h1>
</body>

We may expect the Javascript to run and change the H1 element to be the text of 'New title'. This would not be the case though, we would actually get a console error:

Uncaught TypeError: Cannot set property 'innerText' of null

Why is the element null?

This is because a HTML page is loaded in order from top to bottom, so the script in the head is loaded before it even knows this title element exists, causing the error.

This causes 2 problems, first, we may need to access a HTML element before it is created, and second, we may have a lot of javascript to load which will slow down the page load time.

So adding scripts into the head which need to access the DOM elements can cause a problem.

This is why we often see javascript at the end of the body section, so it allows the elements to be loaded first, and does not block the HTML from loading onto the page:

<head>

</head>

<body>
  <h1 id='title'>Old title</h1>
    <script>
      document.getElementById('title').innerText = 'New title'
    </script>
</body>

The text of New title would now apply.

You will however see some 3rd party Javascript libraries which need the script to be loaded into the head section for the library to function correctly, and there are also some more modern approaches to help with this, called async and defer.

For this example, let's imagine we have 3 simple scripts in our project:

// script1.js
  document.getElementById('title').innerText = '1'
  
// script2.js
  document.getElementById('title').innerText = '2'
    
// script3.js
  document.getElementById('title').innerText = '3'

And we try to link the first one in our head section:

<head>
  <script src="script1.js"></script>
</head>

<body>
  <h1 id='title'>Old title</h1>
</body>

This results in the same console error:

Uncaught TypeError: Cannot set property 'innerText' of null

Again the reason is the same, the script in the head is loaded before it even knows this title element exists.

Now we can take a look at the effect using async and defer have, and we can add these in as an attribute to our opening script tag:

<head>
  <!-- also try replacing async with defer -->
  <script async src="script1.js"></script>
</head>

<body>
  <h1 id='title'>Old title</h1>
</body>

Using both async and defer, will change the title and we now get no console errors.

So, both of these work, but what are they, and what is the difference?

First, we have async, async is short for asynchronous.

For this case, async will download the script alongside the rest of the HTML content, without blocking the page from displaying, then run the script as soon as it has finished downloading.

This is useful since it does not block loading, but there is a drawback.

To see this,I am going to link to all 3 of out scripts, using the async attribute:

<head>
  <script async src="script1.js"></script>
  <script async src="script2.js"></script>
  <script async src="script3.js"></script>
</head>

<body>
  <h1 id='title'>Old title</h1>
</body>

In theory we may expect to only see the value of 3, since this script is loaded last, but this is not always the case, and we may also see numbers from the other scripts too.

This is because we are requesting all 3 scripts in order, but it does not mean they will always come back in the same order.

But why should we even care about this?

Well, consider the popular Bootstrap framework, which is a CSS and Javascript framework for building websites, let's head over to the introduction section from the website:

Bootstrap installation page

First, we include the CSS which is fine, but it is the Javascript which becomes more interesting.

Bootstrap requires 3 separate Javascript files to work correctly.

The order of loading becomes important in cases such as this, because Bootstrap's custom javascript file, which is last, relies on the jQuery library, which is why it is first.

So, using async here may cause problems, this is why Bootstrap recommends placing this at the bottom of the body.

This means things work correctly, but it still has to wait on the HTML content loading first.

Defer on the other hand, will run the scripts in the order they appear, after both the scripts and the page content has loaded:

<head>
  <script defer src="script1.js"></script>
  <script defer src="script2.js"></script>
  <script defer src="script3.js"></script>
</head>

<body>
  <h1 id='title'>Old title</h1>
</body>

Now we should only see the value of 3 (the last script) each time in the browser.

Here is a quick recap:

ASYNC

  • Will download the script alongside the rest of the HTML content.
  • Browser will attempt to render the page while downloading.
  • Will run the script as soon as it has finished downloading.
  • No guarantee scripts will run in source order or will not stop content from displaying.
  • Best to use with multiple scripts which do not depend on each other

DEFER

  • Will load multiple scripts in order.
  • Does not block page content from rendering.
  • Scripts will run as soon as both the page content, and scripts have finished downloading.
  • Best to use with multiple scripts which need to run in order.

- By Chris Dixon

Chris Dixon is a web developer and teacher. Teaching thousands of students mainly in Vue.js React.js, WordPress and web development.