I’m struggling with loading JS files in sequential order, despite having looked at various other SO posts and documentation on async
and defer
. My code structure is as follows:
<script src="lang.js"></script> <!--dynamically loads either eng.js or fra.js--> <script src="task1.js"></script> <!--all of the task*.js depend on eng.js/fra.js--> <script src="task2.js"></script> <script src="task3.js"></script> <script src="task4.js"></script> <script> // inline JS; depends on all task*.js </script>
The contents of lang.js
are as follows:
let langScript = document.createElement("script") // FR is a boolean defined earlier langScript.setAttribute("src", FR ? "fra.js" : "eng.js"); langScript.setAttribute("async", "false"); let head = document.head; head.insertBefore(langScript, head.firstElementChild);
By my understanding, these scripts should load and execute in the order eng.js
/fra.js
-> task*.js
-> inline, but this doesn’t seem to be the case (at least in Chrome and Firefox). How should I modify my code such that it executes in the correct order? (I would prefer not to use callbacks if possible as I don’t want to change the individual JS files too much.)
Advertisement
Answer
Without using import()
You could do something like the following:
- Remove the
<script>
elements for each of thetask*.js
files from the document - Add an event listener for the
load
event of the inserted language script - Create each of the
task*.js
scripts inside that event listener - Add event listeners for the
load
event of eachtask*.js
and use them to resolve aPromise
, which is combined to form a globalPromise
- Wait on that global
Promise
in the inline script.
Doing that, the relevant parts of lang.js
would become:
const langScript = document.createElement('script'); langScript.setAttribute('src', FR ? 'fra.js' : 'eng.js'); const fullyLoaded = new Promise(resolve => { langScript.addEventListener('load', () => { const taskPromises = []; for (let i = 1; i < 5; i++) { const script = document.createElement('script'); script.setAttribute('src', `task${i}.js`); taskPromises.push(new Promise(resolve => { script.addEventListener('load', resolve); })); head.insertBefore(script, head.firstElementChild); } resolve(Promise.all(taskPromises)); }); }); const head = document.head; head.insertBefore(langScript, head.firstElementChild);
and the document would look something like:
<html> <head> <script src="lang.js"></script> <script> window.addEventListener('load', async () => { await fullyLoaded; console.log('start of inline'); }); </script> </head> </html>
None of the other scripts would need to be modified.
With this scheme:
lang.js
is loaded firsteng.js
/fra.js
is completely loaded secondtask1.js
throughtask4.js
are completely loaded in any order- inline scripts are run last
You will need to look at whether this manual deferral causes the loading to take to long; mocking this up locally has all the scripts loaded anywhere from 150ms to 450ms.
Using import()
Effectively the same as the above, but using the import()
function-like keyword, lang.js
becomes:
const src = FR ? './fra.js' : './eng.js'; const fullyLoaded = import(src).then(() => Promise.all([ import('./task1.js'), import('./task2.js'), import('./task3.js'), import('./task4.js') ]));
There are some differences in how JavaScript code is run inside something that is import
ed like this. The big ones are the imposition of strict mode, and the isolation of context, so you will most likely need to store any global variables explicitly onto the window
variable, if you aren’t already, for the eng.js
, fra.js
and task*.js
files.