load and execute order of scripts
Asked Answered
M

6

349

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 <head> or <body> 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 :-)?

Monogenesis answered 25/1, 2012 at 1:33 Comment(1)
Have you looked at Loading Scripts Without Blocking by Steve Souders? It's a bit dated now, but still contains some valuable insights into browser behavior given a specific script loading technique.Voluminous
B
447

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.

Braud answered 25/1, 2012 at 1:38 Comment(18)
Thanks for the answer, but the problem is the script is dynamically added to the page, which means it is considered to be async. Or does that only work in <head>? And my experience is also that they're executed in document order?Monogenesis
@Monogenesis - If it's dynamically added, then it is async and the execution order is indeterminate unless you write code to control it.Braud
Just, Kolink states the opposite...Monogenesis
@Monogenesis - OK, I've modified my answer to say that async scripts load in an indeterminate order. They can be loaded in any order. If I were you, I would not count on Kolink's observation being the way it always is. I know of no standard that says a dynamically added script has to be run immediately and has to block other scripts from running until it's loaded. I would expect that to be browser dependent and also perhaps dependent upon environmental factors (whether the script is cached, etc...).Braud
I agree on your concerns. But effectively, the new script, appended to the end of the body, seems to behave like unparsed tag soup - the document parser is yet halted until the inline script is run.Monogenesis
@Monogenesis - If you want to count on that blocking behavior, then you will have to both find a specification that says it's supposed to be that way now and forever and test a slew of browsers to prove it works that way. What are you really trying to accomplish that makes this so important one way or the other? Treat it as async and you'll be safe either way. Or include it inline and you know it will serialized.Braud
I never said I wanted to :-) But such code is already used on a very big production site, and so I asked for specs/references.Monogenesis
@Monogenesis - I added a couple references to the end of my answer that explain some of it.Braud
... don't get this thread at all, one should set the async property to true or false to determine how a script loads.Costive
defer attribute just saved me big time on a project. Thanks for the great breakdown! Never knew about that...Rotherham
Is there a difference between defer and scripts before </body>? From what I've gathered, both seem to execute at roughly the same time.Fourthly
@RuudLenders - Scripts marked with defer run when the parser finishes so that is pretty close to the same as scripts at the end of the body.Braud
@Braud What about downloading? Would a deferred script in the <head> be downloaded earlier than a script before </body>?Fourthly
@RuudLenders - That is up to the browser implementation. Encountering the script tag earlier in the document, but marked with defer gives the parser the opportunity to start its download sooner while still postponing its execution. Note that if you have a lot of scripts from the same host, then starting the download sooner may actually slow down the downloading of others from the same host (as they compete for bandwidth) that your page is waiting on (that are not defer) so this could be a double edged sword.Braud
This was very well worded. Load dependent scripts and css ansnchronously in parallel and control execution order: github.com/jhabdas/fetch-injectVoluminous
What about module loaders? I think they are the modern approach to easily load multiple scripts in an async way and keeping the script order in tact.Dol
@BluE - I added some comments for modules in <script type="module">.Braud
THANK YOU @Braud for adding the module information. I was pulling my hair out wondering why 'modules' were not getting executed in the order with other, non-ordered type=text/javascript scripts. Thus, if I want to ensure module type scripts are processed in the same order as the text/javascript ones, I should just mark all the test/js script tags as defer? Will this slow up my page load?Hippy
J
42

A great summary by @addyosmani

enter image description here

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

Jahncke answered 21/2, 2019 at 5:5 Comment(0)
T
16

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.

Turning answered 25/1, 2012 at 1:42 Comment(4)
I can confirm that behavior. But there are hints on our feedback pages, that it might only work when test.php is cached. Do you know any spec/reference links about this?Monogenesis
link.js isn't blocking. Use a script similar to your php one to simulate a long download time.Armadillo
This answer is incorrect. It is not always the case that "dynamically added scripts are executed as soon as they are appended to the document". Sometimes this is true (e.g. for old versions of Firefox), but usually it is not. The execution order, as mentioned in jfriend00's answer, is not determinate.Conferee
It doesnt make sense that the scripts are excecuted in the order they appear on the page regardless of if they are inline or not. Why then would the Google tag manager snippet and many others I have seen, have code to insert a new script above all other script tags in the page ? It would not make sense to do this, if the above scripts have already been loaded surely ?? or am I missing something.Criminality
W
12

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'])
Wad answered 17/6, 2019 at 23:15 Comment(4)
Does not work consistently, If you try 10 times, some times it will be out of orderDisappointment
The script.async = false actually worked for me, even when attaching files to document.head. Seems like async defaults to true when attaching dynamically like this. Thanks!Montmartre
It worked for me. I agree with @MontmartreBusiek
I confirmed that @Wilt's guess is correct. The default value was true.. omg..Busiek
S
1

I had trouble understanding how to get an embedded module-script to execute before the onload event happens. The answers above helped a lot but let me add a partial answer about what fixed my particular problem of misunderstanding the "Load and execute order of scripts".

I first used ... which caused an odd problem that it worked when loading the page normally, but not when running it in debugger on FireFox. That made debugging very difficult.

Note: Scripts whose type is "module" always have an implicit "deferred" attribute which means they don't stop the parsing of html, which means the onload-event can happen before the script gets executed. I did not want that. But I did want to use type="module" to make my un-exported JavaScript functions and variables invisible to other scripts on the same page.

I tried different options but thanks to the above answers I gained the insight that if you add the async -attribute to a script of type module it means that the script loads asynchronously BUT once it is loaded it executes immediately.

But in my case this was a script embedded in an HTML page. THEREFORE it meant nothing needed to load "asynchronously". It was already loaded with the page, since it was embedded in it. Therefore it with this change did get executed immediately -- which is what I wanted.

So I think it is worthwhile to point out this specific case because it is somewhat counter-intuitive: To get an embedded script executed IMMEDIATELY you must add the ASYNC attribute to its tag.

Ordinarily one might think that "async" means something happens asynchronously, in indeterminate order, not immediately. But the thing to realize is that "async" causes asynchronous LOADING, but immediate EXECUTION after the loading is complete. And when the script is embedded, no loading needs to be done, and therefore you get immediate execution.

Summary: Use

     <script type="module" async> ... </script>

to get a module-script embedded to an HTML-page to execute immediately.

Seanseana answered 18/7, 2021 at 15:42 Comment(1)
This seemed to work at first, but if all other javascript files are already cached, sometimes this will not work and it ends up running at the bottom of the list.Anticlastic
A
-1

Perfect match for your query!

If none of the solutions worked for you then please refer to the below solution of mine which I have developed from my side.

I was also looking for the solution but after searching a lot, I summarized my code as below which is working perfectly for me!!

This is useful when you want functionality such that after previous script has fully loaded then and then only load next script!

just create a file named jsLoadScripts.js and insert it into the head or at the bottom of the body.

//From Shree Aum Software Solutions
//[email protected]

//script incrementor for array
scriptIncrementor = 0;

//define your script urls here
let scripts = [
    "https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js",
    "jsGlobalFunctions.js",
    "jsDateParser.js",
    "jsErrorLogger.js",
    "jsGlobalVariables.js",
    "jsAjaxCalls.js",
    "jsFieldsValidator.js",
    "jsTableClickEvent.js",
    "index.js",
    "jsOnDocumentReady.js",
];

//it starts with the first script and then adds event listener to it. which will load another script on load of it. then this chain goes on and on by adding dynamic event listeners to the next scripts! 
function initializeScripts() {
    var script = document.createElement("script");
    script.type = "text/javascript";
    script.src = scripts[scriptIncrementor];
    document.head.appendChild(script);
    script.addEventListener("load", function () {
        loadNextScript();
        scriptIncrementor++;
    });
}

// this function adds event listener to the scripts passed to it and does not allow next script load until previous one has been loaded!
function loadNextScript() {
    if (scriptIncrementor != scripts.length - 1) {
        var script = document.createElement("script");
        script.type = "text/javascript";
        script.src = scripts[scriptIncrementor + 1];
        document.head.appendChild(script);
        script.addEventListener("load", function () {
            loadNextScript();
            scriptIncrementor++;
        });
    }
}

// start fetching your scripts
window.onload = function () {
    initializeScripts();
};

This may cause you some speed related issues so, you can call function initializeScripts() with your custom needs!

Abraxas answered 26/6, 2022 at 10:18 Comment(1)
This is a nice technique (but lacking error handling and being very slow due to sequential loading), but it doesn't at all answer the question how script loading works.Monogenesis

© 2022 - 2024 — McMap. All rights reserved.