Nitkov
Nitkov

Reputation: 468

JS scripts not downloaded and evaluated sequentially

I have 3 <script> tags as the last elements in <body>. They load external .js scripts:

    ...
  </div>
  <script src="https://somewhere.com/scripts/script1.js"></script>
  <script src="https://somewhere.com/scripts/script2.js"></script>
  <script src="https://somewhere.com/scripts/script3.js"></script>
</body>
</html>

All scripts contain only one line: console.log(Start script X), where X is the script number. I was expecting for the scripts to be evaluated sequentially and the output to be:

Start script 1
Start script 2
Start script 3

But the output seems random. To be more precise, it looks like the order of evaluation depends solely on the order in which the scripts finish downloading.

Was I right to expect evaluation order to mimic the order they were referenced in HTML? If not, how do I make sure that they are evaluated in the correct order (besides merging them all in one .js file)?

Upvotes: 7

Views: 594

Answers (2)

Kinglish
Kinglish

Reputation: 23664

Option 1: defer

This article illuminates the defer/asyc script tag attributes. Scripts marked defer are executed (after parsing completes) in the order which they are defined in the markup.

 <script src="https://somewhere.com/scripts/script1.js" defer></script>
 <script src="https://somewhere.com/scripts/script2.js" defer></script>
 <script src="https://somewhere.com/scripts/script3.js" defer></script>
 <script src="https://somewhere.com/scripts/script4.js" defer></script>

Option 2: Managed Sequential Loading

This script should load the files in one by one in order and your desired sequence should be preserved

const scripts = [
  "https://somewhere.com/scripts/script1.js",
  "https://somewhere.com/scripts/script2.js",
  "https://somewhere.com/scripts/script3.js",
  "https://somewhere.com/scripts/script4.js"
];

let sctr = 0;
const doScript = () => {
  if (sctr++ >= scripts.length) {
    console.log('all scripts loaded syncronously');
    return;
  }
  const scriptPromise = new Promise((resolve, reject) => {
    const script = document.createElement('script');
    document.body.appendChild(script);
    script.onload = resolve;
    script.onerror = reject;
    script.async = true; // they're loading one at a time, so async is ok
    script.src = scripts[sctr];
  });

  scriptPromise.then(() => {
      console.log(scripts[sctr], 'loaded');
      doScript();
    },
    (err) => {
      console.error(scripts[sctr], 'failed', err);
      // halting
    });
}

Upvotes: 3

Lajos Arpad
Lajos Arpad

Reputation: 76804

Your observation is correct. I understand your expectation, but such ordering of evaluation would imply either that the files are downloaded in sequence rather than in paralell, or that they are evaluated in sequence rather than in paralell.

Rendering them to be downloaded or executed in sequence would be troublesome. What if a script never downloads? Should the subsequent scripts hang on forever? It would be much worse than the current situation.

What if the scripts are not interdependent? Should they wait for each-other even if there is no point in doing so? Of course not.

So the actual behavior is correct, but you are right that this implies some problems as well.

In general, the solution for this problem is to break up the scripts into classes and functions that can be executed when it is ideal, rather than at the time of the script download and also have an onload event in the body tag or a load event being created via AddEventListener.

Let's assume that there is an f1 function in script1, an f2 function in script2, ...

then you can do something like this:

window.addEventListener('load', (event) => {
  f1();
  f2();
  //...
});

You can even do it like this:

function load() {
    f1();
    f2();
    //...
}

and then add it to the body:

<body onload="load()">
    <!-- ... -->
</body>

Upvotes: 5

Related Questions