load and execute order of scripts

load and execute order of scripts

There are so many different ways to include JavaScript in a html page. I know about the following options:

inline code or loaded from external URI
included in or tag [1,2]
having none, defer or async attribute (only external scripts)
included in static source or added dynamically by other scripts (at different parse states, with different methods)

Not counting browserscripts from the harddisk, javascript:URIs and onEvent-attributes [3], there are already 16 alternatives to get JS executed and I’m sure I forgot something.
I’m not so concerned with fast (parallel) loading, I’m more curious about the execution order (which may depend on loading order and document order). Is there a good (cross-browser) reference that covers really all cases? E.g. http://www.websiteoptimization.com/speed/tweak/defer/ only deals with 6 of them, and tests mostly old browsers.
As I fear there’s not, here is my specific question: I’ve got some (external) head scripts for initialisation and script loading. Then I’ve got two static, inline scripts in the end of the body. The first one lets the script loader dynamically append another script element (referencing external js) to the body. The second of the static, inline scripts wants to use js from the added, external script. Can it rely on the other having been executed (and why :-)?

Solutions/Answers:

Solution 1:

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.

Related:  What is a proper way of end-to-end (e2e) testing in Vue.js

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-confirming 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.

Solution 2:

The browser will execute the scripts in the order it finds them. If you call an external script, it will block the page until the script has been loaded and executed.

To test this fact:

// file: test.php
sleep(10);
die("alert('Done!');");

// HTML file:
<script type="text/javascript" src="test.php"></script>

Dynamically added scripts are executed as soon as they are appended to the document.

To test this fact:

<!DOCTYPE HTML>
<html>
<head>
    <title>Test</title>
</head>
<body>
    <script type="text/javascript">
        var s = document.createElement('script');
        s.type = "text/javascript";
        s.src = "link.js"; // file contains alert("hello!");
        document.body.appendChild(s);
        alert("appended");
    </script>
    <script type="text/javascript">
        alert("final");
    </script>
</body>
</html>

Order of alerts is “appended” -> “hello!” -> “final”

If in a script you attempt to access an element that hasn’t been reached yet (example: <script>do something with #blah</script><div id="blah"></div>) then you will get an error.

Overall, yes you can include external scripts and then access their functions and variables, but only if you exit the current <script> tag and start a new one.

Solution 3:

A great summary by @addyosmani

enter image description here

Shamelessly copied from https://addyosmani.com/blog/script-priorities/

Solution 4:

After testing many options I’ve found that the following simple solution is loading the dynamically loaded scripts in the order in which they are added in all modern browsers

loadScripts(sources) {
    sources.forEach(src => {
        var script = document.createElement('script');
        script.src = src;
        script.async = false; //<-- the important part
        document.body.appendChild( script ); //<-- make sure to append to body instead of head 
    });
}

loadScripts(['/scr/script1.js','src/script2.js'])