0

I have built a chrome extension which one of the functions is to check for jQuery on the page and if it does not exist then to add it to the page. I repurposed some code from an older extension and whilst it works as expected (99% of the time) I want to understand the usage of setTimeout in this context.

Both setTimeout(addjQuery) and addjQuery() seem to work just fine in most cases. Here is the code as it stands and like I said it does work almost always.

Updated to ask a specific question... I am injecting both jquery and myLibrary into the page using the syntax below. As you can see in the onMyLibraryLoaded function that I am trying to use jQuery to attach a blur event to all inputs. This works almost always... Is there a reason why it would sometimes throw an error that jQuery is not defined. My only explanation is that occasionally the myLibrary loads faster than jquery and fires before jquery was ready? Is that right? How would I go about fixing this? await?

if (!window['jQuery']) {  
    setTimeout(addjQuery);    
}
if (!window['myLibrary']) {  
    setTimeout(addMyLibrary);    
}

function addjQuery() {
    const script = document.createElement('script');
    script.src = 'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.1/jquery.min.js';
    script.onload = onjQueryLoaded;
    document.body.appendChild(script);        
}

function addMyLibrary() {
    const script = document.createElement('script');
    script.src = 'https://jrags/myLibrary.min.js';
    script.onload = onMyLibraryLoaded;
    document.body.appendChild(script);        
}


function onMyLibraryLoaded(){
    $(document).on("blur", "#someInput", function(){
       console.log("Input blurred");
    })
}

function onjQueryLoaded(){
    console.log('jquery loaded sucessfully')
}
  • What's your question? – isherwood Dec 09 '22 at 15:37
  • 2
    Does this answer your question? [Why is setTimeout(fn, 0) sometimes useful?](https://stackoverflow.com/questions/779379/why-is-settimeoutfn-0-sometimes-useful) (Note that calling `setTimeout` with no second `delay` argument treats the delay as 0.) – Wyck Dec 09 '22 at 15:37
  • Completing what @Wyck mentions, you have to keep in mind that: `If this parameter is omitted, a value of 0 is used, meaning execute "immediately", or more accurately, the next event cycle. Note that in either case, the actual delay may be longer than intended; see Reasons for delays longer than specified below.`. It's worthy to read the documentation https://developer.mozilla.org/en-US/docs/Web/API/setTimeout – Arnau Dec 09 '22 at 16:29
  • 1
    I think this should be re-opened. Asker has moved the goalpost with an edit: it's no longer just asking to "understand the usage of setTimeout", but instead is asking about a race condition loading libraries out of sequence. Perhaps the part of the question about setTimeout should be removed as it's irrelevant. – Wyck Dec 09 '22 at 17:07

1 Answers1

0

Both of those libraries take time to fetch over the internet. And potentially a different amount of time each time you run. You've initiated the loading in the next event cycle by delaying the call to creating the script element, but that's inconsequential to what you are observing. To be clear, the setTimeout calls you have written are unnecessary.

If you look at your browser's developer tools to see which network requests happened and in which order they completed, you'll find that in the cases where it fails, the https://jrags/myLibrary.min.js library will have loaded before the jQuery library was loaded. But you've used a jQuery function in the handler for loading your library (specifically script.onload = onMyLibraryLoaded; and onMyLibraryLoaded calls $(document), which requires jQuery to be loaded.)

You have a couple of approaches for fixing the problem. One approach is to defer loading of your script until jQuery is loaded. But really it's probably best to get the loads starting as soon as possible. So a different, more efficient approach is to just defer blurring #someInput until jQuery has also loaded.

My best recommendation is to use a Promise for the loading of each library and execute your blur when both promises have resolved.

function addLibrary(src) {
  return new Promise(resolve => {
    const script = document.createElement('script');
    script.src = src;
    script.onload = resolve;
    document.body.appendChild(script);        
  })
}

let p1 = addLibrary('https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.1/jquery.min.js')
  .then(() => console.log('jQuery loaded'));
let p2 = addLibrary('https://jrags/myLibrary.min.js')
  .then(() => console.log('myLibrary loaded'));
Promise.all([p1, p2]).then(() => {
  $(document).on("blur", "#someInput", function(){
     console.log("Input blurred");
  })
});

or with await:

let p1 = addLibrary('https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.1/jquery.min.js');
let p2 = addLibrary('https://jrags/myLibrary.min.js');
await p1;
await p2;
$(document).on("blur", "#someInput", function(){
     console.log("Input blurred");
  })
Wyck
  • 10,311
  • 6
  • 39
  • 60