4

I am trying to understand defer with a simple example. I have the below JS code which takes 5 seconds to run.

home.js

function wait(ms){
    var start = new Date().getTime();
    var end = start;
    while(end < start + ms) {
      end = new Date().getTime();
   }

 }

 wait(5000);

I am using this in home.html as shown below:

<html>
    <head>
        <script defer src="home.js"></script>
    </head>
<body>
    <h1>Hello World!</h1>
</body>
</html>

I find that if use defer or put my script at the end of body tag ( in my understanding both are equivalent), hello world appears only after 5 seconds. But if I use asnyc 'hello world' appears immediately. Why is this?

Salman A
  • 262,204
  • 82
  • 430
  • 521
codingsplash
  • 4,785
  • 12
  • 51
  • 90
  • 1
    Which browser? In Chrome it should appear immediately. Probably your browser doesn't support `defer`. – Serg Jan 31 '19 at 10:29
  • I am using Chrome v 71.x – codingsplash Jan 31 '19 at 10:30
  • `defer` essentially moves the script to the end of ``. `async` runs the script while the document is parsed and displayed. –  Jan 31 '19 at 10:33
  • @ChrisG yeah, but then why does 'Hello World' appear after 5 seconds, as is appearing before the script tag? – codingsplash Jan 31 '19 at 10:35
  • 1
    Because the script is executed before the first render, and before `DOMContentLoaded` fires. As far as I understand, `defer` is used to run a script that does something to the DOM before it's displayed for the first time. –  Jan 31 '19 at 10:42
  • If you add more content to your page (like 2-3 screenful of html) you'll notice that the page starts rendering _before_ 5 seconds. So your conclusion is incorrect. – Salman A Jan 31 '19 at 10:44
  • https://bitsofco.de/async-vs-defer/ –  Jan 31 '19 at 10:46
  • @ChrisG, the article says "The defer attribute tells the browser to only execute the script file once the HTML document has been fully parsed." That means only after the HTML in this case, the h1 tag, is parsed, the script should have run. Then why is h1 appearing with a delay? – codingsplash Jan 31 '19 at 10:49
  • The order is 1. html is parsed into DOM 2. deferred script runs 3. DOM is rendered –  Jan 31 '19 at 10:50

3 Answers3

1

To answer your immediate question, the Script Element documentation on MDN will help.

Defer:

Scripts with the defer attribute will prevent the DOMContentLoaded event from firing until the script has loaded and finished evaluating.

Async:

This is a Boolean attribute indicating that the browser should, if possible, load the script asynchronously.

An async script will not hold up the DOMContentLoaded event from firing.

There are several better ways of causing dom content to appear after a delay that will not block the UI thread like you are doing; this is a very bad practice.

An extract from DOMContentLoaded:

Synchronous JavaScript pauses parsing of the DOM. If you want the DOM to get parsed as fast as possible after the user has requested the page, you can make your JavaScript asynchronous

The normal practice would be to append dom content after the document is loaded. You could still do this with a timeout but usually the additional dom content would be loaded after some other asynchronous action like a response received from fetch.

Here is an example of appending dom content after a delay:

function wait(ms){
  window.setTimeout(function() {
    var element = document.createElement("h1")
    element.innerText = "Hello World!"
    document.body.appendChild(element)
  }, ms)
}

 wait(5000);
<html>
    <head>
        <script src="home.js"></script>
    </head>
<body>
    
</body>
</html>
BlueWater86
  • 1,773
  • 12
  • 22
1

Let's looks at this (open debug console to see the output):

test.js

document.addEventListener("DOMContentLoaded", function(event) {
    console.log("[4]: 'DOMContentLoaded' event fired!");
});

function wait(ms){
    console.log("[2]: 'wait' started!");
    var start = new Date().getTime();
    var end = start;
    while(end < start + ms) {
        end = new Date().getTime();
    }
    console.log("[3]: 'wait' finished!");
}

wait(5000);

test.html

<html>
    <head>
        <script defer src="test.js"></script>
    </head>
<body>
    <h1>Hello World!</h1>
    <script>console.log('[1]: DOM is parsed!')</script>
</body>
</html>

Console log:

[1]: DOM is parsed!
[2]: 'wait' started!
[3]: 'wait' finished!
[4]: 'DOMContentLoaded' event fired!

Now you can see these steps happen:

  1. DOM is loaded and parsed.
  2. The waitfunction is run (because it is deferred and run after the DOM is parsed but before the DOMContentLoaded event is fired).
  3. 'defer` script is finished.
  4. The DOMContentLoaded event is fired (only after all defer scripts are executed in the order they go in HTML).
  5. HTML is rendered but some resources like stylesheets and images can be still loading.

The DOM rendering time can be different in different browsers and ways of hosting the HTML file: in Chrome (as my tests showed) in case of http:// the DOM is rendered before defer scripts are run, but in case of file:/// the DOM is rendered after defer scripts are run and their synchronous code is completed.

Summary (from your question in the comments):

  • defer and async scripts are loaded asynchronously.
  • defer scripts will be executed in the order in which they are placed in HTML.
  • defer scripts will be executed after the DOM is loaded and parsed, but before the DOMContentLoaded is fired.
  • async script could be loaded any time and run in any order (most likely in the order as they are loaded). If such a script happens to be loaded before the DOM is parsed, you can't expect this script to be able to find some elements in HTML. Also, in this case, the browser doesn't wait for the script to be loaded and executed to fire the DOMContentLoaded event.
Serg
  • 6,742
  • 4
  • 36
  • 54
  • 1
    So defer will not block parsing, but it will block rendering? What advantage does differ give compared to async? Also if I put the script at the end of body tag does it block rendering as well? – codingsplash Jan 31 '19 at 10:42
  • I added 5000 `
    ` to the page and it loaded immediately.
    – Salman A Jan 31 '19 at 10:46
  • 1). Scripts with `defer` will be executed in the order in which they are placed in HTML. 2). Scripts with `async` could be loaded before the DOM is parsed, so you can't always expect such a script to be able to find some element in HTML. – Serg Jan 31 '19 at 10:50
1

@Sergey's answer is good but doesn't really explain. The schematic diagram under 4.12.1 here shows that when defer is specified, the browser waits with the start of script execution until the entire HTML is parsed. The key is parsing is not rendering. Rendering starts at the same time as the execution of the deferred script, but in this particular case the script is blocking in a heavy loop. If the wait() function doesn't block, rendering has a chance to effectively update the DOM right after parsing finished and in parallel with the non-blocking deferred script execution. The DOMContentLoaded will still be fired when the script finished though.

function wait(ms){
  return new Promise((resolve, reject) => {setTimeout(() => resolve(), ms)});
}

 wait(3000).then(() => console.log('done'));
<html>
    <head>
        <script defer src="home.js"></script>
    </head>
<body>
    <h1>Hello World!</h1>
</body>
</html>
marekful
  • 14,986
  • 6
  • 37
  • 59
  • So, if I understand right, the script tag is blocking parsing. So if we use defer or async, it does not block parsing. But rendering will be blocked based on the type of operations that happen in JS. If the JS code fetched by the script is blocking, it will block rendering. If it is async, it does not block rendering. So effectively there are two blocking operations in the code? – codingsplash Jan 31 '19 at 10:57
  • With the "blocking" wait function, there aren't any "free ticks" for the event loop to go into rendering. The execution of wait() is scheduled on the main task queue and while your version blocks the event loop until it's finished, in my example the promise is scheduled on the microtask queue. It's a pretty complex subject, you can find a lot of good reading about how event loops are controlled in browsers. – marekful Jan 31 '19 at 11:18