Load and Execute Order of Scripts

load and execute order of scripts

If you aren't dynamically loading scripts or marking them as defer or async, then scripts are loaded in the order encountered in the page. It doesn't matter whether it's an external script or an inline script - they are executed in the order they are encountered in the page. Inline scripts that come after external scripts are held until all external scripts that came before them have loaded and run.

Async scripts (regardless of how they are specified as async) load and run in an unpredictable order. The browser loads them in parallel and it is free to run them in whatever order it wants.

There is no predictable order among multiple async things. If one needed a predictable order, then it would have to be coded in by registering for load notifications from the async scripts and manually sequencing javascript calls when the appropriate things are loaded.

When a script tag is inserted dynamically, how the execution order behaves will depend upon the browser. You can see how Firefox behaves in this reference article. In a nutshell, the newer versions of Firefox default a dynamically added script tag to async unless the script tag has been set otherwise.

A script tag with async may be run as soon as it is loaded. In fact, the browser may pause the parser from whatever else it was doing and run that script. So, it really can run at almost any time. If the script was cached, it might run almost immediately. If the script takes awhile to load, it might run after the parser is done. The one thing to remember with async is that it can run anytime and that time is not predictable.

A script tag with defer waits until the entire parser is done and then runs all scripts marked with defer in the order they were encountered. This allows you to mark several scripts that depend upon one another as defer. They will all get postponed until after the document parser is done, but they will execute in the order they were encountered preserving their dependencies. I think of defer like the scripts are dropped into a queue that will be processed after the parser is done. Technically, the browser may be downloading the scripts in the background at any time, but they won't execute or block the parser until after the parser is done parsing the page and parsing and running any inline scripts that are not marked defer or async.

Here's a quote from that article:

script-inserted scripts execute asynchronously in IE and WebKit, but
synchronously in Opera and pre-4.0 Firefox.

The relevant part of the HTML5 spec (for newer compliant browsers) is here. There is a lot written in there about async behavior. Obviously, this spec doesn't apply to older browsers (or mal-conforming browsers) whose behavior you would probably have to test to determine.

A quote from the HTML5 spec:

Then, the first of the following options that describes the situation
must be followed:

If 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

The element must be added
to the end of the list of scripts that will execute when the document
has finished parsing associated with the Document of the parser that
created the element.

The task that the networking task source places on the task queue once
the fetching algorithm has completed must set the element's "ready to
be parser-executed" flag. The parser will handle executing the script.

If the element has a src attribute, and the element has been flagged
as "parser-inserted", and the element does not have an async attribute

The element is the pending parsing-blocking script of the Document of
the parser that created the element. (There can only be one such
script per Document at a time.)

The task that the networking task source places on the task queue once
the fetching algorithm has completed must set the element's "ready to
be parser-executed" flag. The parser will handle executing the script.

If the element does not have a src attribute, and the element has been
flagged as "parser-inserted", and the Document of the HTML parser or
XML parser that created the script element has a style sheet that is
blocking scripts
The element is the pending parsing-blocking script of
the Document of the parser that created the element. (There can only
be one such script per Document at a time.)

Set the element's "ready to be parser-executed" flag. The parser will
handle executing the script.

If the element has a src attribute, does not have an async attribute,
and does not have the "force-async" flag set
The element must be added
to the end of the list of scripts that will execute in order as soon
as possible associated with the Document of the script element at the
time the prepare a script algorithm started.

The task that the networking task source places on the task queue once
the fetching algorithm has completed must run the following steps:

If the element is not now the first element in the list of scripts
that will execute in order as soon as possible to which it was added
above,
then mark the element as ready but abort these steps without
executing the script yet.

Execution: Execute the script block corresponding to the first script
element in this list of scripts that will execute in order as soon as
possible.

Remove the first element from this list of scripts that will execute
in order as soon as possible.

If this list of scripts that will execute in order as soon as possible
is still not empty and the first entry has already been marked as
ready, then jump back to the step labeled execution.

If the element has a src attribute The element must be added to the
set of scripts that will execute as soon as possible of the Document
of the script element at the time the prepare a script algorithm
started.

The task that the networking task source places on the task queue once
the fetching algorithm has completed must execute the script block and
then remove the element from the set of scripts that will execute as
soon as possible.

Otherwise The user agent must immediately execute the script block,
even if other scripts are already executing.


What about Javascript module scripts, type="module"?

Javascript now has support for module loading with syntax like this:

<script type="module">
import {addTextToBody} from './utils.mjs';

addTextToBody('Modules are pretty cool.');
</script>

Or, with src attribute:

<script type="module" src="http://somedomain.com/somescript.mjs">
</script>

All scripts with type="module" are automatically given the defer attribute. This downloads them in parallel (if not inline) with other loading of the page and then runs them in order, but after the parser is done.

Module scripts can also be given the async attribute which will run inline module scripts as soon as possible, not waiting until the parser is done and not waiting to run the async script in any particular order relative to other scripts.

There's a pretty useful timeline chart that shows fetch and execution of different combinations of scripts, including module scripts here in this article: Javascript Module Loading.

Async script execution order

Historically, browsers would stop parsing the DOM until after the script tag is loaded.

Modern browsers do roughly the same thing, but to load resources in parallel it will read ahead in the DOM to find upcoming requests.

So to answer your question, the first script tag is not guaranteed to load before the second script, but it should execute before before the async script.

Load ordering of dynamically added script tags

I can mention some alternatives to overcome that requirement:

  1. Use a library to inject dependencies (AMD or CommonJS modules)
    Just use modules. ES2015: import/export, or CommonJS: require().
  2. Create the script tag programmatically and set the callback onload in order to react when the script has been loaded asynchronously. The attribute async = true is set by default.
  3. If you are allowed to modify the scripts to inject, then add a line at the end of scripts with an object or array that keeps track of the scripts already loaded.
  4. You can fetch the scripts as text (XMLHttpRequest), then, build a string with the scripts in the required order and, finally, execute the text-script via eval()
  5. And the less recommended option but frequently used, set a setInterval to check if the script was already executed.

I recommend going for the first option. But for academic purposes, I'm going to illustrate the second option:

Create the script tag programmatically and set the callback onload in order to react when the script has been loaded asynchronously.

I want to recommend a reading about script loaders: Deep dive into the murky waters of script loading, half hour worth spending!

The following example is a small module to manage scripts injection, and this is the basic idea behind it:

let _scriptsToLoad = [
'path/to/script1.js',
'path/to/script2.js',
'path/to/script3.js'
];

function createScriptElement() {
// gets the first script in the list
let script = _scriptsToLoad.shift();
// all scripts were loaded
if (!script) return;
let js = document.createElement('script');
js.type = 'text/javascript';
js.src = script;
js.onload = onScriptLoaded;
let s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(js, s);
}

function onScriptLoaded(event) {
// loads the next script
createScriptElement();
};

In this plunker you can test the injection of scripts asynchronously in a specific order:

  • https://plnkr.co/edit/b9O19f

The main idea was to create an API that allows you interact with the scripts to inject, by exposing the following methods:

  • addScript: receive an URL or a list of URLs for each script to be loaded.
  • load: Run the task to load scripts in the specified order.
  • reset: Clear the array of scripts, or cancels the load of scripts.
  • afterLoad: Callback executed after every script has been loaded.
  • onComplete: Callback executed after all scripts have been loaded.

I like Fluent Interface or method chaining technique, so I built the module that way:

  scriptsLoader
.reset()
.addScript("script1.js")
.addScript(["script2.js", "script3.js"])
.afterLoad((src) => console.warn("> loaded from jsuLoader:", src))
.onComplete(() => console.info("* ALL SCRIPTS LOADED *"))
.load();

In the code above, we load first the "script1.js" file, and execute the afterLoad() callback, next, do the same with "script2.js" and "script3.js" and after all scripts has been loaded, the onComplete() callback is executed.

Execution order when dynamically injecting script elements

So it turns out, according to the spec, that the script executes before the onload event assigned to that <script> element.

Here's the relevant quote, emphasis mine:


  1. Create a script, using the script block's source, the URL from which the script was obtained, the script block's type as the scripting language, and the script settings object of the script element's Document's Window object.

    If the script came from a resource that was fetched in the steps above, and the resource was CORS-cross-origin, then pass the muted errors flag to the create a script algorithm as well.

    This is where the script is compiled and actually executed.

  2. Decrement the ignore-destructive-writes counter of neutralized doc, if it was incremented in the earlier step.

  3. Fire a simple event named afterscriptexecute that bubbles (but is not cancelable) at the script element.

  4. If the script is from an external file, fire a simple event named load at the script element.

    Otherwise, the script is internal; queue a task to fire a simple event named load at the script element.

Order of JavaScript script tag executions not guaranteed in major browsers?

The execution order of these non-dynamically added script tags should be purely sequentially in every browser:

Snippet from this link:

JavaScript statements that appear
between <script> and </script> tags
are executed in order of appearance;
when more than one script appears in a
file, the scripts are executed in the
order in which they appear.

However, things could change as soon as you're:

  • triggering asynchronous processing through your own code (not in this example)
  • adding script tags dynamically
  • using the defer attribute.

Javascript inline scripts and defer scripts order of execution

scripts load synchronously unless specified to load asynchronously using the async attribute, what the defer attribute does is that it loads the script only after DOM is loaded. If you append a script dynamically, it will load asynchronously.

In you scenario,

This should be the chain of execution:

  • check polyfill script executes
  • lib1
  • lib2
  • lib3
  • myOwnScriptFile
  • polyfills (downloaded only after the parser has finished execution)

To ensure all your scripts load, in the order you want them too, you could dynamically load all scripts with something like :

check if the browser is IE:

IF IE 
load polyfills, and load other scripts in the onload event of the polyfills script.
ELSE
load all other scripts


Related Topics



Leave a reply



Submit