nikjohn
nikjohn

Reputation: 21852

Javascript async defer order of execution in case of multiple files in Single Page Applications

I am trying to improve the page load performance of my page, that is implemented on EmberJS.

I am considering using asyc and defer on our Javascript files. All other optimizations have already been done (moving the scripts to the bottom of the page, adding async and defer to analytics tags etc).

Now, as per ember-cli specs, the generated index.html has two script tags - one vendor JS file and one application JS file.

If I am to implement async and defer, I need to ensure that my vendor JS file is loaded before my application JS file, to make sure the latter has all required code to initialize the application.

I understand that the order in which the scripts are fetched and parsed are different when defined with async and defer, as defined here

enter image description here

My question is this:

If you have multiple JS files in the same page, is there a way to fetch and execute them in a stipulated order? I'm looking for something like callbacks/promises in async requests, but in terms of the actual script tag itself.

Upvotes: 6

Views: 5843

Answers (2)

Bernard Leech
Bernard Leech

Reputation: 764

Things may have changed for the better since this question was first posted, but it seems that in 2019 you can defer your scripts and have them processed in the order the script tags are written in your html document. Adding defer to both your vendor script and your main script will cause them to load in parallel, not block parsing of the html document, and be processed in order on document parse completion.

The 4.12.1.1 Processing model section of whatwg's scripting document goes into quite a bit of detail that I'll try to summarise here:

  • If the script's type is "classic" (not type="module"), and the element has a src attribute, and the element has a defer attribute, and the element has been flagged as "parser-inserted", and the element does not have an async attribute

  • then add the element to the end of the list of scripts that will execute in order as soon as possible associated with the node document of the script element at the time the prepare a script algorithm started.

Check out the link for full details, but essentially what it seems to be saying is that deferred scripts will be processed in the order they are parsed in the html document.

MDN agrees:

Scripts with the defer attribute will execute in the order in which they appear in the document.

One other important point to note (from the same MDN document):

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

It's also worth noting that neither whatwg nor MDN says anything about placing your script tag in the head or at the bottom of the body of the html document. If all of your scripts have the defer attribute, they will be processed in occurrence order when the html document has completed parsing. Putting the script tags in the header will mean they will start to download early in the html document parsing process, rather than later which is the case when they are placed at the bottom of the body. But of course that also depends on how many other resources you are downloading from the same host in parallel.

Rambling a bit now, but in summary, for best non-blocking performance:

  • Place all your script tags as early in the html document as possible
  • Add them in the order that you want them to be processed
  • Add the defer attribute to all of them (if they don't need to be processed synchronously or as soon as downloaded)
  • For scripts that need to be processed as soon as downloaded, add the async attribute. HTML parsing will continue while the script is downloading - will pause when the script has finished downloading and while the script is executed - and will resume once the script has finished executing.
  • For scripts that need to be processed as soon as downloaded, and that have side effects such as modifying the DOM, don't add async or defer. HTML parsing will pause while the script is downloading - will stay paused when the script has finished downloading and while the script is executed - and will resume once the script has finished executing.

Update July 2020:

In Chrome, downloading and parsing of synchronous scripts (those without async or defer) has improved quite a bit. Downloading and parsing are done on separate threads - and the download thread streams the file into the parser as it downloads.

In combination with <link rel="preload"> in your <head>, it's possible that your file could be downloaded by the time the HTML parser reaches your <script> tag - which means it won't need to pause and can execute the script right away:

Better downloading and parsing of synchronous scripts

The image above is taken from the video Day 2: Chrome web.dev Live 2020 - What's New in V8 / Javascript - the section in which they explain updates to downloading and parsing is about 4 minutes long, but well worth the watch.

Upvotes: 7

Dimitris Karagiannis
Dimitris Karagiannis

Reputation: 9358

I can think of two approaches.

a) Do what you said. i.e. have a script tag which has two chained promises inside, each of which creates a new script tag, appends it to the DOM, adds an onload event function which would be the promise's resolve function and lastly sets its src attribute to the resource's URL. When the script from the first promise loads, the second promise should execute and do the same thing.

b) Take the middle road. Have the vendor file in the head, to load synchronously, and have the application file at the very bottom of the document, to load after everything else finished.

In my opinion the first option is an overkill.

EDIT: Example for a)

<script>
var p = new Promise(function(resolve, reject) {
    var scriptTag = document.createElement('script');
    document.head.appendChild(scriptTag);
    scriptTag.onload = resolve;
    scriptTag.src = 'URL_to_vendor_file';
});

p.then(function() {
  var scriptTag = document.createElement('script');
    document.head.appendChild(scriptTag);
    scriptTag.src = 'URL_to_application_file';
};
</script>

Note: The example above can be written and without the use of promises

Upvotes: 1

Related Questions