You have created the HTML for your website. You have completed the JavaScript code to bring the pages to life. Now, you only need to load the script files into the HTML now.

But there’s a question facing you.

Which JavaScript loading strategy (async vs defer) should you follow?

Should you go with async or defer? Which one is best? Or the correct question is : which one suits your application?

Developers have a habit of not paying close attention to how script files are loaded in the browser. However, selecting the proper loading strategy can make a significant difference in your application.

With this post, you are going to have absolutely clarity about JavaScript Loading in the context of async vs defer.

1 – How does the browser loads scripts?

When the browser parses the HTML code and finds a <script> tag, it stops building the DOM. and starts to execute the script. The same happens even in the case of external scripts that are provided using <script src="">.

This is the default behaviour of the browser and you can understand it in three easy steps:

  • Wait for script to download
  • Execute the script
  • Continue processing the HTML document

The default behaviour results in a couple of problems:

Firstly, scripts are not able to see DOM elements below their declaration. This means you cannot add any handlers to the DOM elements that are declared below the <script> tag. Below is an example that demonstrates this problem. You cannot add a click event handler to the Click Me button inside the script.js file.

<html lang="en-US">
	<head>
		<meta charset="utf-8">
		<title>Javascript Loading Demo</title>
		<script src="script.js"></script>
	</head>
	
	<body>
		<button>Click Me!</button>
	</body>
</html>

Second problem shows up if the script happens to be bulky (which is often the case with modern javascript applications).

If that’s the case, the browser will take a long time to download the script file. Coupled with a bad internet connection, this can lead to a horrible user experience as the users will keep waiting for the page to become active.

2 – How should we load the scripts?

The first problem where scripts can’t see the DOM elements below them has a simple workaround that’s being used for ages.

You put the script at the very bottom of the HTML document and just above the closing </body> tag.

See below example:

<!DOCTYPE html>

<html lang="en-US">
	<head>
		<meta charset="utf-8">
		<title>Javascript Loading Demo</title>
	</head>
	
	<body>
		<button>Click Me!</button>
		<script src="script.js"></script>
	</body>
</html>

And voila, the script can now see the button element!

Technically, this workaround also solves the second problem. More or less.

Since the script now resides at the end of the document, it won’t block the page content from showing up. The browser will download and execute the script after it is done with the HTML page. The user experience won’t be too horrible.

However, this opens up another issue.

What happens if your HTML document is rather long?

In this scenario, the browser will take forever to load the document. And only then it will see the script and start downloading it. If the script also happens to be bulky, it will create an even bigger delay before the page becomes useful.

The page might be visible to the user. But there’s not much the user can do except stare at it and click buttons to find that nothing works.

Scripts add life to a web page. Therefore, till the script is not executed, the HTML document is nothing but a dead page.

Clearly, this approach can lead to an even worse user experience if certain conditions are met.

So what is the ideal solution?

Modern browsers support a couple of <script> attributes that you can choose for an ideal solution. These attributes are known as defer and async. Before you do a JavaScript async vs defer, its important to understand these terms individually.

3 – JavaScript defer attribute

The JavaScript defer attribute tells the browser not to wait for the script to execute.

When the browser finds a script tag with the defer attribute, it continues processing the HTML and building the DOM. Also, the browser continues loading the script in the background.

Once the DOM is built and ready, the browser goes ahead and executes the script.

The below illustration shows how defer works in practice.

javascript async vs defer explanation
JavaScript defer attribute

The script download overlaps the process of building the DOM. But execution takes place only after the entire HTML is parsed.

Here’s how you can use defer.

<html lang="en-US">
	<head>
		<meta charset="utf-8">
		<title>Javascript Loading Demo</title>
		<script src="script.js" defer></script>
	</head>
	
	<body>
		<button>Click Me!</button>
	</body>
</html>

Some key takeaways from defer attribute:

  • defer is a boolean attribute. Adding it to the script tag means true. Not having it means false.
  • defer only applies to external script files. You can’t use it with in-line JavaScript code within the script tag.
  • Script tag using defer doesn’t block the page. So users will see the page being rendered as the browser builds the DOM.
  • In the case of multiple scripts using defer, they will be downloaded in parallel but executed in order. For example, you may have an HTML document pointing to two scripts – script1.js and script2.js. If the script2.js gets downloaded earlier, it will still wait and will run only after script1.js is downloaded and executed. This is important because many times, you may load a JavaScript library followed by your own script that depends on the library. In that case, you would want the library to execute first.
<script src="script1.js" defer></script>
<script src="script2.js" defer></script>
  • Scripts using defer execute when the DOM is ready but before the DOMContentLoaded event.

For the last point, take the below HTML document as an example

<html lang="en-US">
	<head>
		<meta charset="utf-8">
		<title>Javascript Loading Demo</title>
		<script>
		document.addEventListener('DOMContentLoaded', () => alert("DOM ready after defer!"));
		</script>
		<script src="script.js" defer></script>
	</head>
	
	<body>
		<button>Click Me!</button>
	</body>
</html>

Also, below are the contents of the script.js file.

function createParagraph() {
	const para = document.createElement('p');
	para.textContent = 'You made the mistake of clicking the button!';
	document.body.appendChild(para);
}

const buttons = document.querySelectorAll('button');

for (const button of buttons) {
	console.log("Adding Click Listeners to the Button")
	button.addEventListener('click', createParagraph);
}

If you load the above HTML in the browser, you would see the console.log() message printed before the DOM ready alert pops up. Basically, the DOMContentLoaded event gets fired after the script file is executed by the browser.

4 – JavaScript async attribute

To define a script as async, you have to add the async attribute to the script tag.

Technically, async seems pretty similar to defer. Just like defer, it also makes the script download non-blocking. Browsers continue to download the scripts as the DOM is being loaded.

But async has a fundamental difference from defer.

Scripts using async are totally independent.

In other words, these scripts run as soon as they are loaded. And when they run, the browser stops processing the HTML document. This means that async can potentially block the building of the DOM.

The below illustration describes how async works.

javascript async strategy
JavaScript async attribute

Here’s an example on how to use JavaScript async vs defer

<!DOCTYPE html>

<html lang="en-US">
	<head>
		<meta charset="utf-8">
		<title>Javascript Loading Demo</title>
		<script src="script.js" async></script>
	</head>
	
	<body>
		<button>Click Me!</button>
	</body>
</html>

When the browser reads the <script> tag, it starts downloading the script.js file. Also, it continues processing the HTML and builds the DOM. If the script file finishes downloading before the HTML is processed, it will still run.

Now, you might consider this as a bad thing. But it all depends on the use-case.

You should use async scripts for background processing or third party functionality that does not depend on other parts of the application. For example, think about Google ad links or Google analytics or any other process that runs safely without depending on the DOM.

Here are some key takeaways of async attribute:

  • async is also a boolean attribute. Absence of async means async attribute is false.
  • The async attribute applies only to external scripts. Just like defer.
  • async scripts are independent. Browsers don’t block execution of async scripts. But async scripts can block the DOM building once they are downloaded.
  • In the case of multiple scripts, async doesn’t wait for other scripts and other scripts also don’t wait for the async scripts.

5 – Using async and defer together

There is one small point that needs discussion.

What if you use async and defer together?

And yes, you can use async and defer together by adding both attributes to the script tag.

But what does the browser choose in this particular case?

On modern browsers, async gets the preference. However, on older browsers that don’t support async, the script follows the defer approach.

This begs for some caution.

You should choose async  and defer together only when the overall application can work with the async approach of loading the script files. Think of scripts that are totally independent from the rest of the application.

For all other cases, defer is usually the safest approach.


JavaScript async vs defer is an important consideration you should take when building your web applications.

At the bare minimum, it will improve the efficiency of your application. But it can also end up changing the user experience.

If you found this post useful, consider sharing it with friends and colleagues. You can also connect with me other platforms.

ProgressiveCoder Newsletter

Twitter

LinkedIn

Youtube

Categories: Javascript

0 Comments

Leave a Reply

Avatar placeholder

Your email address will not be published. Required fields are marked *