In jQuery, how to deal with parallel script loading?

272 Views Asked by At

I am stuck with this in an app development and don't know what to try. I have the following function:

// Function to load Scripts on the fly
$.loadScript = function(url, arg1, arg2) {
    var cache = false,
        callback = null;
    //arg1 and arg2 can be interchangable
    if ($.isFunction(arg1)) {
        callback = arg1;
        cache = arg2 || cache;
    } else {
        cache = arg1 || cache;
        callback = arg2 || callback;
    }
    
    var that = this;
    var load = true;

    var deferred = jQuery.Deferred();
    if (jQuery.isFunction(callback)) {
        deferred.done(function(){
            callback.call(that);
        });
    };

    if( url.constructor === Array ){
        function loadScript(i) {
            if (i < url.length) {
                var el = url[i];

                //check all existing script tags in the page for the url
                if (window.loadedScripts.indexOf(el) === -1) {
                    window.loadedScripts.push(el);
                    //didn't find it in the page, so load it
                    $.ajax({
                        url: el,
                        success: function(e){
                            __info('Loaded script. url: '+el, 'verbose');
                            loadScript(i + 1);
                        },
                        complete: function(e){
                            if(typeof e.done !== 'function'){
                                $(function () {
                                    $('<script>')
                                        .attr('type', 'text/javascript')
                                        .text(e.responseText)
                                        .prop('defer',true)
                                        .appendTo('head');
                                })
                            }
                        },
                        dataType: 'script',
                        cache: cache
                    });
                }
                //-----------------
            } else {
                deferred.resolve();
            }
        }
        loadScript(0);

        return deferred;
    } else {
        //check all existing script tags in the page for the url
        if (window.loadedScripts.indexOf(url) === -1) {
            window.loadedScripts.push(url);
            //didn't find it in the page, so load it
            $.ajax({
                url: url,
                success: function(e){
                    __info('Loaded script. url: '+url, 'verbose');
                    deferred.resolve();
                },
                complete: function(e){
                    if(typeof e.done !== 'function'){
                        $(function () {
                            $('<script>')
                                .attr('type', 'text/javascript')
                                .text(e.responseText)
                                .prop('defer',true)
                                .appendTo('head');
                        })
                    }
                },
                dataType: 'script',
                cache: cache
            });
        } else {
            deferred.resolve();
        };
    }
};

This function works similar to $.getScript, but loads several scripts (or only one), fires a callback at end and introduces cache parameter to deal with custom cache of this app.

It already works fine, except in the case when several scripts are requesting loading in parallel. When this occurs, second set of scripts enter the function, watches that first script (common for the blocks) are loading and don`t load it (but will need to wait for it). But, in this case, in first block the first script is not loaded already and in the second block, this script is needed but is skipped.

Example of use:

<script type="text/javascript">
    $.loadScript([
        "//code.highcharts.com/highcharts.src.js",
        "//code.highcharts.com/modules/data.js",
        "//code.highcharts.com/modules/treemap.js"
    ], true, function(){ 
        $.ajax({
             type: 'POST',
             url: '/skip-process/charts/graph1',
             success: function(data){
                 console.log($.parseJSON(data));
                 var chart = new Highcharts.Chart($.parseJSON(data));
             }
        });
    });
</script>

How to deal with several scripts requesting load at the same time the same script and they are not waiting for it? Thanks a lot.

1

There are 1 best solutions below

3
charlietfl On

I would approach this using $.getScript and using the promise it returns and using then() instead of passing a callback into the function.

Following is not well tested but should be very close

$.loadScript = function (url) {
  // create loadedScripts array if doesn't already exist
  const cache = window.loadedScripts = window.loadedScripts || [];
  // always use array even when string passed in, reduces duplicate code
  const normalizeUrl = (url) => (Array.isArray(url) ? url : [url]);

  let urlArr = normalizeUrl(url).filter((u) => {
    if (cache.includes(u)) {
      return false;
    }
    cache.push(u);
    return true;
  });

  // if all cached just return a resolved promise
  if (!urlArr.length) {
    return Promise.resolve();
  }
  
  // create initial getScript promise
  const reqs = $.getScript(urlArr.shift());

  while (urlArr.length) {
    const u = urlArr.shift();
    // chain a new then() for each url
    reqs.then(() => $.getScript(u));
  }
  //return getScript promise chain
  return reqs;
};

$.loadScript([
    'remote-script-1.js', 
    'remote-script-2.js'])
 .then(() => {
  console.log('All Loaded');    
});