1

I have a function which at some time does document.getElementsByTagName("script") and it wants to know for each script if it was loaded already.

I'd like to get a Promise which is fulfilled if it was loaded and pending if it's still going on, so I can attach next steps with .then(...) and be sure they get executed.

I could attach a load event I suppose, but if the script was already loaded, I wouldn't ever get the event.

I could work around this by making some assumptions, namely that I am in control of all dynamic script tags, and for static script tags, the document.currentScript is always the last script loaded so far and all preceding scripts are done loading, assuming they were all static. But those assumptions might not always be true.

So, is there a way to check the status and generate Promise that depending on the status will either start as fulfilled (resolved) or be fulfilled by the load event firing?

Gunther Schadow
  • 1,490
  • 13
  • 22
  • it might be more or less possible with [global/sub namespace](https://stackoverflow.com/questions/881515/how-do-i-declare-a-namespace-in-javascript), also read [this](https://stackoverflow.com/questions/9827827/detect-when-list-of-scripts-are-all-loaded-in-javascript-using-namespaces) and [this](https://stackoverflow.com/questions/28550944/is-there-a-way-to-detect-when-external-js-has-loaded-and-executed) – Raymond Nijland Jul 03 '22 at 10:04
  • Is there any reason not to load these scripts as lazy loaded modules? – Konrad Jul 03 '22 at 11:21
  • What are the circumstances in which `document.getElementsByTagName("script")` will return a script element that has not yet been loaded? – MikeM Jul 03 '22 at 12:36
  • @MikeM when script elements are dynamically added. They are there already but not loaded yet. When I add them myself, I can make sure I get the load event. But if someone else puts them in, and I want to keep track, then I would need a status flag of some kind to know if it's not too late to add a load handler. – Gunther Schadow Jul 03 '22 at 17:52
  • 1
    To handle async-loaded dynamically added scripts you may be able to use a MutationObserver to add the load event listener to script elements as soon as they are added to the document. I may be back later to have a go. – MikeM Jul 03 '22 at 18:34
  • 1
    @GuntherSchadow How do you keep track of scripts added by someone else? A mutation observer is probably fast enough to attach the event handler before it gets loaded. – Bergi Jul 03 '22 at 18:36
  • [This](https://stackoverflow.com/a/74420159/9971404) might help you. – lonix Nov 13 '22 at 10:44

2 Answers2

0

A MutationObserver can be used to add a load event listener to dynamically created script elements.

A Map is used to store the promises that will be resolved when the load event fires.

const scriptMap = new Map();

const observer = new MutationObserver(mutations => {
  for (const mutation of mutations) {
    for (const node of mutation.addedNodes) {
      if (node.nodeName === 'SCRIPT' && node.getAttribute('src') != null) {
        scriptMap.set(node, new Promise(resolve => {
          node.addEventListener('load', () => resolve());  
        }));
      }
    }    
  }  
});

Start observing:

observer.observe(document, {
  childList: true,
  subtree: true
});

Define a function that returns a promise that will be pending if a script has not yet loaded or resolved otherwise:

const load = script => scriptMap.get(script) ?? Promise.resolve();

Example usage:

const scriptElements = document.getElementsByTagName('script');

for (let script of scriptElements) {
  await load(script);
}

Use scriptMap.values() to get the promises of dynamically created scripts:

await Promise.all(scriptMap.values());

For efficiency's sake, stop observing when it is no longer required:

observer.disconnect();
MikeM
  • 13,156
  • 2
  • 34
  • 47
0

Adding script elements to the DOM will cause them to load asynchronously, not in time for code that immediately follows to make use of them. You need to use a callback function for the script's onload event. Here is an example helper function that does so.

function loadAsync(url, callback) {
  var s = document.createElement('script');
  s.setAttribute('src', url);
  s.onload = callback;
  document.head.insertBefore(s, document.head.firstElementChild);
}
Giuseppe Canale
  • 470
  • 7
  • 15