如何动态加载JS脚本,并发安全?

时间:2017-02-18 11:10:11

标签: javascript concurrency callback

在我的项目中,我使用非常标准的loadScript方法来动态加载JS脚本,一旦完成,就会触发回调:

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);
  }
};

在我的项目情况下,我正在使用基于组件的系统。要说明问题,请在同一页面上设想两个Google地图组件。两者都是组件的实例。在他们的构造函数中,我调用了这个方法,就像这样...

  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));

  }

我预料到如果组件的两个实例加载相同的脚本,则不应加载两次。出于这个原因,您可以看到loadScript助手内部如何维护已加载脚本的全局数组:app.scripts [url] = true;

但是,此策略不起作用,因为多个loadScript调用彼此非常接近地启动。由于第一个未完成(正在进行中),第二个呼叫开始另一个。

我考虑立即设置" loading"全局数组中脚本的状态,但这仍然让我想知道第二个调用如何能够听到第一个调用准备就绪,因为它确实需要这样的触发器,否则回调会很快触发。

我有一种感觉,我或许可以过度思考这个?

编辑,包括解决此问题的代码,基于@siam的回答,添加了一些调整:

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);
  }
};

请注意,该方法现在名为loadScriptEvent,第二个参数是事件名称,而不是回调。这个想法是,尽管两个组件几乎可以同时调用它,但它们都会听同一个单一事件。但这还不够,如果第一个请求已经忙于加载脚本,那么第二个请求仍然必须停止。我已经意识到加载状态。您可以这样称呼此方法:

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));

这很好用。组件实例彼此不了解,但脚本仍然只是异步加载一次,并且我们可以在完成后立即运行代码。

1 个答案:

答案 0 :(得分:1)

以下代码可能会让您大致了解如何完成此操作:

// 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 
});