1

I have several scripts I need to dynamically include in a specific order. I have looked at and tried several answers here: How to include multiple js files using jQuery $.getScript() method

My problem is that the answers here either don't work in my application due to loading the files asynchronously or greatly over complicating the matter due to loading external scripts. The scripts I need to dynamically load are internal. (ie: On my site's server)

In addition to many other attempts, Ive tried:

$.when(
            $.getScript('/1.js'),
            $.getScript('/2.js'),
            $.getScript('/3.js'),
            $.getScript('/4.js'),
            $.getScript('/5.js'),
            $.Deferred(function (deferred) {
                $(deferred.resolve);
            })
        ).done(function() {         
            //Do Something
        });

but this does not load the scripts in proper order and kicks out a slew of errors.

Furthermore, The scripts I need to load are internal and I dont see the need for all the AJAX

Ive also tried other variations to no avail and was hoping someone could illustrate how I could dynamically load internal scripts one after another, preferably without AJAX calls and over complicating?

Stuart
  • 193
  • 1
  • 10
  • 1
    Unless you can include the scripts in the HTML, which doesn't seem to be the case, you'll *have* to use Ajax or some network request to fetch them, right? – CertainPerformance Nov 27 '19 at 10:25
  • @CertainPerformance Im cloudy on the need for AJAX but if its necessary even for internal scripts that does shed light on the matter for me but Im still in the dark as to how to load these synchronously and would welcome & appreciate any guidance. I'm still reading elsewhere regardless. Thanks – Stuart Nov 27 '19 at 10:27
  • If they have to be done in sequence then you would need to chain the `$.getScript` calls calls in the callback of each previous one. Are you using SPA architecture for the site? I ask as I'm trying to determine the reason for async loading of scripts; it's usually a lot more hassle than it's worth. – Rory McCrossan Nov 27 '19 at 10:30
  • @RoryMcCrossan Im sorry, IDK what SPA Architecture is. Likely not. Just a handful of scripts that are dependent on each prior script. It works wonderfully by including them in HTML, The site just doesnt need them until a user clicks a button – Stuart Nov 27 '19 at 10:36
  • Ah ok, so if this is being done for optimisation then it's not quite the correct approach. I'd suggest using bundling and minification of your JS (and CSS) instead. That way all scripts are loaded once and cached. Any subsequent page requests will then have no load time for the JS/CSS as they are cached. It also avoids the complexity of the AJAX requests you have to make, and also the loading stutters you'll get. – Rory McCrossan Nov 27 '19 at 10:38
  • Good Points, Thank You. I just dont want to load these scripts until needed by User Interaction – Stuart Nov 27 '19 at 10:47

3 Answers3

2

getScript returns a Promise-like object, so you can await each call of it in a loop for them to be processed in serial:

(async () => {
  for (const path of ['/1.js', '/2.js', '/3.js']) {
    await $.getScript(path);
  }
  // Do something
})()
  .catch((err) => {
    // Something went wrong
  });
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • I thank you immensely. Could I interest you in wrapping this in a similar manner to the $.when(... script I posted? Im not sure how to make sense of: "(async () => {" and be able to take a step – Stuart Nov 27 '19 at 10:33
  • `await` can only be used inside an `async` function, thus the async and IIFE. Async functions return Promises, and the Promise will reject if any of the awaited items inside the function rejected, so `.catch` is needed on the outside to handle errors. You can also use try/catch inside the IIFE while awaiting, but `.catch(`ing the Promise looks a lot nicer IMO. You don't need to wrap this in `$.when(` or anything, that'll make things unnecessarily complicated; the code in the answer above should work as-is – CertainPerformance Nov 27 '19 at 10:37
  • Awesome, Thanks. Im rolling up my sleeves now – Stuart Nov 27 '19 at 10:39
  • If you're "Performance" moniker is related to motorsports, You'll love my site. BTW, Since we are using AJAX and since Ive read that getScript does not cache, Is there a simple one liner we could add here so as to enable a users machine to cache these scripts for another day while they're at it? – Stuart Nov 27 '19 at 10:42
  • Adding a caching mechanism would be a lot more complicated. You might store each script result in `localStorage` or something like that, and then check `localStorage` before calling `getScript`. Add a version number that gets bumped every time you change the script so that users download the most recent version instead of loading the old one from their local system. – CertainPerformance Nov 27 '19 at 10:46
  • If im sure these scripts are already healthy and work fine, Is there any harm in removing the .catch(... and why the () before catch? I know just enough to be dangerous and am still learning but Im pretty sure what you shared here is the latest and greatest syntax to accomplish this. Just slightly over my head – Stuart Nov 27 '19 at 10:51
  • Thank You CertainPerformance. You Solved it!! Now upto a 98 On Google Page Speed Insights – Stuart Nov 27 '19 at 11:02
  • The `()` before the `catch` is needed to invoke the `async` function. Without that, it's just an unused function expression, but you want to invoke it so that its code runs. The `.catch` *is* needed for a good UI - there are many factors outside of your control that may interrupt the connection between the client and your server, and putting a `.catch` onto it allows you to observe and handle those sorts of errors more gracefully than silent failure. – CertainPerformance Nov 27 '19 at 11:06
  • m gettng a syntax error from this in older browsers seemingly due to async/await. Any ideas are welcomed – Stuart Dec 02 '19 at 21:50
  • Whenever you're using newer language features (which professional code *should* use for good readability), to make the scripts compatible with obsolete browsers, the most reliable solution is to use [Babel](https://babeljs.io/) to transpile the syntax automatically. If you want to target IE, you want output in ES5 syntax. – CertainPerformance Dec 02 '19 at 22:05
  • Sry for extending discussion in comments but hopefully it will be beneficial to others as well: I don't want to use babel primarily due to loading adtl dependencies (Side note, My site performs beautifully w/o any "Bootstrap", "Font Awesome" or similar bullshit and I dont want to begin by using Babel.) There has to be a way of accomplishing the original idea for older and newer browsers, hopefully in native JS and definitely without any addtl dependencies and bloat and polyfills like can be seen here: https://stackoverflow.com/questions/47684102/js-async-and-await-to-work-in-internet-explorer – Stuart Dec 03 '19 at 11:19
  • Babel is a *development* dependency, not a *production* dependency, luckily - it won't have any negative effects on users, other than letting those with obsolete browsers run your code. I'd consider Babel essential for any reasonably professional project; it keeps the source code short, clean, and readable, when you can code in the latest and greatest version of the language. – CertainPerformance Dec 03 '19 at 22:20
  • If you can't use any modern language features and can only use ES5, you can have each `getScript` recursively call itself with the next path, but that's a whole lot uglier (and jQuery Promise-like objects have a number of problems) – CertainPerformance Dec 03 '19 at 22:21
  • Thanks for your help Certain. Tried your earlier example via Babel but noticed still gives syntax error on iOS 9 or Win XP, Ive gotta leave them behind and move on – Stuart Dec 05 '19 at 10:51
0

For anyone who is using jQuery I have improved @adeneo script so it will load all scripts in the specified order. It doesn't do chain loading, so it's very fast, but if you want even faster, change the 50 ms wait time.

$.getMultiScripts = function(arr, path) {

    function executeInOrder(scr, code, resolve) {
        // if its the first script that should be executed
        if (scr == arr[0]) {
            arr.shift();
            eval(code);
            resolve();
            console.log('executed', scr);
        } else {
            // waiting
            setTimeout(function(){
                executeInOrder(scr, code, resolve);
            }, 50);
        }
    }

    var _arr = $.map(arr, function(scr) {

        return new Promise((resolve) => {
            jQuery.ajax({
                type: "GET",
                url: (path || '') + scr,
                dataType: "text",
                success: function(code) {
                    console.log('loaded  ', scr);
                    executeInOrder(scr, code, resolve);
                },
                cache: true
            });
        });

    });
        
    _arr.push($.Deferred(function( deferred ){
        $( deferred.resolve );
    }));
        
    return $.when.apply($, _arr);
}

How to use:

var script_arr = [
    'myscript1.js', 
    'myscript2.js', 
    'myscript3.js'
];

$.getMultiScripts(script_arr, '/mypath/').done(function() {
    // all scripts loaded
});
Eugene
  • 905
  • 1
  • 10
  • 22
-1

In pure js

['/1.js', '/2.js',  '/3.js', '/4.js', '/5.js'].forEach(function(script) {
    var scriptElement = document.createElement("script");
    scriptElement.src = script;

    // append script element where you want (head or body)
    document.head.appendChild(scriptElement);
});
Plush
  • 1