Scripts: async, defer

The scripts are heavier in modern browsers than in HTML: the download size is larger, and it takes more time to process.

Once the browser loads HTML meets a <script>...</script> tag, it won’t be able to continue setting up the DOM. The script should be executed by it right now. The same scenario works for the external scripts. In other words, the browser must hold on until the script loads, execute it and process the rest of the page only after.

It leads to the following two significant problems:

  1. The scripts are not capable of seeing the DOM elements below them. Hence, they can’t add handlers.
  2. In case there exists a massive script at the the page top, “the page will be blocked”. The page content won't be seen by the users until it downloads and runs, like this:
<!DOCTYPE html>
<html>
  <head>
    <title>Title of the Document</title>
  </head>
  <body>
    <p> Before script...</p>
    <script src="https://www.w3docs.com/js/long.js?speed=1"></script>
    <!-- This is not visible until the script loads. -->
    <p> After script</p>
  </body>
</html>

It is also possible to place a script at the bottom of the page. In that case, it will see the elements above and the page content won't be blocked by it, like this:

<!DOCTYPE html>
<html>
  <head>
    <title>Title of the Document</title>
  </head>
  <body>
    All content should be placed above the script!
    <script src="https://www.w3docs.com/js/long.js?speed=1"></script>
  </body>
</html>

However, this is not a perfect solution. For instance, if the script is seen by the browser, only after the entire HTML document is downloaded. Note that for large HTML documents, it can be a noticeable delay. Things like this can be invisible to people who use faster connections. But, consider that many people still have a slow internet connection. Fortunately, there exist two <script> attributes that can solve the possible issues. These attributes are called defer and async.

Defer

Defer informs the browser that it should continue working with the page, load the script “in the background” and then run the script when it loads. Let’s demonstrate the example above, using the defer attribute:

<!DOCTYPE html>
<html>
  <head>
    <title>Title of the Document</title>
  </head>
  <body>
    <p> Before script</p>
    <script src="https://www.w3docs.com/js/long.js?speed=1"></script>
    <!-- Seen right away -->
    <p> After script </p>
  </body>
</html>

So, the scripts with the defer attribute don’t block the page. They always runonce the DOM is arranged, but prior to the DOMContentLoaded event, like in the example below:

<!DOCTYPE html>
<html>
  <head>
    <title>Title of the Document</title>
  </head>
  <body>
    <p> Before scripts</p>
    <script>
      document.addEventListener('DOMContentLoaded', () => alert("DOM ready after defer!")); // (2)
    </script>
    <script src="https://www.w3docs.com/js/long.js?speed=1"></script>
    <p> After scripts .</p>
  </body>
</html>
p>The page content immediately appears. The DOMContentLoaded event waits for the script that was deferred. It only occurs when the script (2) is loaded and executed.

The scripts that are known as deferred, on their turn, maintain their order. So, in the event of having a large script initially, then a lesser one, then the latter script waits, like this:

<!DOCTYPE html>
<html>
  <head>
    <title>Title of the Document</title>
  </head>
  <body>
    <script src="https://www.w3docs.com/js/long.js"></script>
    <p>loaded long script </p>
    <script src="https://www.w3docs.com/js/small.js"></script>
    <p>loaded small script </p>
  </body>
</html>

The browsers scan the scripts page, downloading them in parallel. It is done for improving performance. As you can notice, the example above shows the process of downloading both scripts in parallel. First, it is made by the small.js. But as required by the specification, the scripts should execute in the document order. So, the small.js waits for the long.js to execute.

An important thing to note: Defer is only for external attributes. It is ignored when the <script> tag doesn’t have src.

Async

Async signifies that the script is completely independent:

  1. The async scripts are not waited for by the page: the content is processed and presented.
  2. DOMContentLoaded and the async scripts don’t have to wait for one another. ( the DOMContentLoaded event might occur before and after an async script, in case the latter finishes loading after the page is complete. Either it can happen after an async script, in case the latter is short or was located in the HTML-cache).
  3. Other scripts never wait for async scripts and vice versa.

So, in case of having multiple scripts, they might execute in any sequence. The one that loads first-runs first, like this:

<!DOCTYPE html>
<html>
  <head>
    <title>Title of the Document</title>
  </head>
  <body>
    <p> Before scripts </p>
    <script>
      document.addEventListener('DOMContentLoaded', () => alert("DOM ready!"));
    </script>
    <script src="https://www.w3docs.com/js/long.js"></script>
    <script src="https://www.w3docs.com/js/small.js"></script>
    <p> After scripts </p>
  </body>
</html>

So, in the above-demonstrated example, the page content appears immediately. The DOMContentLoaded event might occur both before and after async: there are no guarantees. Async scripts don’t wait for one another. And, finally, a small.js goes second but loads before long.js. So, it runs first. That scenario is known as a “load-first” order.

Dynamic Scripts

A script can be added dynamically with JavaScript, as follows:

<!DOCTYPE html>
<html>
  <head>
    <title>Title of the Document</title>
  </head>
  <body>
    <script>
      let script = document.createElement('script');
      script.src = "https://www.w3docs.com/js/long.js";
      document.body.append(script); // (*)
    </script>
  </body>
</html>

The script begins to load as soon as it’s appended to the document (*).

Dynamic scripts act like “async” by default.

In other words, they don’t wait for anything, and vice versa.

The script, which loads first-runs first, as well.

Take a look at the example below:

<!DOCTYPE html> 
<html>
  <head>
    <title>Title of the Document</title>
  </head>
  <body>
    <body>
      <script>
        let script = document.createElement('script');
        script.src = "https://www.w3docs.com/js/long.js";
        script.async = false;
        document.body.append(script);        
      </script>
  </body>
</html>

For instance, here two scripts are added. But, without script.async=false these scripts would execute in the load-first order. With that flag, the sequence is as in the document, like here:

<!DOCTYPE html>
<html>
  <head>
    <title>Title of the Document</title>
  </head>
  <body>
    <script>
      function loadScript(src) {
        let script = document.createElement('script');
        script.src = src;
        script.async = false;
        document.body.append(script);
      }
      // long.js runs first because of async=false
      loadScript("https://www.w3docs.com/js/long.js");
      loadScript("https://www.w3docs.com/js/small.js");
    </script>
  </body>
</html>

Summary

The async and defer attributes are largely used for escaping the most common issues that users can come across. They have one common thing: downloading scripts like that never blocks the page rendering. So, it allows the user to read the page content and get acquainted with it at once. Along with the significant similarities, async and defer have essential differences. For example, async follows the load-first order, while defer keeps the document order.




Do you find this helpful?

Related articles