1

This article helped explain how the browser is pulling in scripts, but what's the best practice to check an object exists before attempting to initialize it?

In linking headroom.js here, I'm currently checking if the window object contains it. Is this okay, or should I be using some form of script.onload ?

<script src="js/main.js"></script>
<script src="js/libs/ScrollTrigger.min.js" defer></script>
<script src="https://unpkg.com/headroom.js@0.9.4/dist/headroom.min.js" async></script>

<script defer>
        document.addEventListener('DOMContentLoaded', function(){
            // setup ScrollTrigger (animate when item comes into view)
            var trigger = new ScrollTrigger({
                offset: {x:0, y:300},
                once: true
            });


            // bind Headroom to nav
            if(window.Headroom){
                init_headroom();
            }else{
                console.log('polling for Headroom '+Date.now());
                let poll_for_headroom = window.setInterval(function(){
                    if(window.Headroom){
                        clearInterval(poll_for_headroom);
                        init_headroom();
                    }
                },20);
            }


More questions: 2a) Will this kind of polling block UI? 2b) Should I be checking if "ScrollTrigger.min.js" exists?

1 Answers1

4

The best practice would be to add a load handler to the script tag whose loading you want to check:

const headroomScript = document.querySelector('script[src*="headroom"]');
headroomScript.onload = () => {
  console.log('Loaded!');
  console.log(typeof window.Headroom);
};
<script src="js/main.js"></script>
<script src="js/libs/ScrollTrigger.min.js" defer></script>
<script src="https://unpkg.com/headroom.js@0.9.4/dist/headroom.min.js" async></script>

To check when all of multiple scripts have loaded, you can use Promise.all:

// this will fail because the `src` of "js/main.js" does not exist in stack snippets:
const proms = [...document.querySelectorAll('script[src]')]
  .map(script => new Promise((resolve, reject) => {
    script.onload = resolve;
    script.onerror = reject;
  }));
Promise.all(proms)
  .then(() => {
    console.log('All scripts are loaded!');
    console.log(typeof window.Headroom);
  })
  .catch((err) => {
    console.log('There was an error');
  });
<script src="js/main.js"></script>
<script src="js/libs/ScrollTrigger.min.js" defer></script>
<script src="https://unpkg.com/headroom.js@0.9.4/dist/headroom.min.js" async></script>

// this will succeed:
const proms = [...document.querySelectorAll('script[src][async]')]
  .map(script => new Promise((resolve, reject) => {
    script.onload = () => {
      console.log(script.src + ' loaded');
      resolve();
    };
    script.onerror = reject;
  }));
Promise.all(proms)
  .then(() => {
    console.log('All scripts are loaded!');
    console.log(typeof window.Headroom);
    console.log(typeof window.jQuery);
  })
  .catch((err) => {
    console.log('There was an error');
  });
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js" async></script>
<script src="https://unpkg.com/headroom.js@0.9.4/dist/headroom.min.js" async></script>

Note that the async attribute for scripts is only effectual for scripts with an external src - the attribute has no effect on inline scripts, eg

<script async>
  // do some stuff
</script>

the async tag will do nothing here.

Neither this approach nor your original approach will block the UI.

CertainPerformance
  • 356,069
  • 52
  • 309
  • 320