0

In my project, I have the pretty standard loadScript method to dynamically load JS scripts, and when done, the callback is fired:

app.utilities.loadScript = function loadScript(url, callback)
{

  if (app.scripts[url]===true) {
    callback();
  } else {

    var head = document.getElementsByTagName('head')[0];
    var script = document.createElement('script');
    script.type = 'text/javascript';
    script.src = url;

    script.onload = function() {
      app.scripts[url] = true;
      callback();
    }
     // Fire the loading
    head.appendChild(script);
  }
};

In my project situation, I'm working with a component-based system. To illustrate the problem, imagine two Google Maps components on the same page. Both are instances of a component. In their constructor, I'm calling the method, like so...

  constructor(selector) {

    super(selector);

    // API load status
    this._api = false;
    // load the maps API
    app.utilities.loadScript('https://maps.googleapis.com/maps/api/js?v=3.exp&key=' + app.config.mapsKey, this.initMap.bind(this));

  }

I anticipated the situation that if two instances of a component load the same script, it should not get loaded twice. For that reason, you can see how inside the loadScript helper, I'm maintaining a global array of already loaded scripts: app.scripts[url] = true;

This strategy does not work, however, because multiple loadScript calls are started very close after each other. Since the first one did not finish yet (in progress), the second call starts another one.

I'm considering immediately setting a "loading" status on the script in a global array, but that would still leave me wondering how the second call can listen to the first one being ready, because it does need such a trigger otherwise the callback fires too soon.

I have a feeling I'm perhaps overthinking this?

Edit: including the code that solves this, based on an answer by @siam, with a little tweak added:

app.utilities.loadScriptEvent = function loadScript(url, eventName)
{

    if (app.scripts[url] == 'loading') {
      return true;
    }

    else {
    app.scripts[url] = 'loading';

    var event = new CustomEvent(eventName);

    // Adding the script tag to the head as suggested before
    var head = document.getElementsByTagName('head')[0];
    var script = document.createElement('script');
    script.type = 'text/javascript';
    script.src = url;

    // Then bind the event to the callback function.
    // There are several events for cross browser compatibility.
    //script.onreadystatechange = callback;

    script.onload = function() {
      app.scripts[url] = true;
      app.window.dispatchEvent(event);
    }
     // Fire the loading
    head.appendChild(script);
  }
};

Note that the method is now named loadScriptEvent, and that the 2nd parameter is an event name, not a callback. The idea is that even though two components could almost simultaneously make a call to this, they both listen to the same, single event. That's not enough though, the 2nd request must still be stopped if the first one is already busy loading the script. I've realized that with a loading state. You'd call this method like this:

app.utilities.loadScriptEvent('https://maps.googleapis.com/maps/api/js?v=3.exp&key=' + app.config.mapsKey, "gmloaded");
app.window.addEventListener('gmloaded',this.initMap.bind(this));

This works nicely. Component instances have no knowledge of each other, yet the script is still loaded only once, asynchronously, and we can immediately run code once it has done so.

4
  • use setInterval to check whether the first request is complete or not and if it is then initiate the second one and clear that interval Commented Feb 18, 2017 at 11:18
  • @siam. I was afraid of an answer like that, but it may be the only way. Note though that the second request has no knowledge of the first one (since these are two component instances unaware of each other). Commented Feb 18, 2017 at 11:48
  • 1
    umm... maybe there's another way. you can create a custom event that gets triggered when the first request completes. and you could also pass the data of the first request with that event. Commented Feb 18, 2017 at 11:59
  • @siam Will explore that option a little further, thanks! Commented Feb 18, 2017 at 12:00

1 Answer 1

1

The following code might give you a rough idea on how it could be done :

// using jQuery

app.utilities.loadScript = function loadScript(url, callback) { 
  ...
  script.onload = function(data) {
        app.scripts[url] = true;
        callback();
        ...
        $(document).trigger('firstRqstCompleted', data);
    }
  ...
}

$(document).on('firstRqstCompleted', function(e, data) {
    // do stuff with first request's data
    // initiate second request 
});
Sign up to request clarification or add additional context in comments.

2 Comments

Thank you for the code, but unfortunately this solution does not solve the problem. The nature of the problem is that both calls to loadscript will be very fast after each other. Therefore, any code you put in onload will not stop the 2nd request from doing the same thing. Just tried this with a custom event handler, it will be called twice.
Your code needed a little tweak (see my edited answer) to avoid onload getting triggered twice still, but the general idea of using an event was the right one, so I marked it as the correct answer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.