greenbandit
greenbandit

Reputation: 2275

jQuery: load scripts in order

I'm trying load some scripts dynamically with jQuery:

var scripts = ['script1.js','script2.js','script3.js'];

$.each(scripts , function(i, val) {
     $.getScript(val, function() {
     console.log('loaded '+ val);
});

But sometimes the order of loaded scripts changes. How can I load each script after the previous one has successfully loaded?

Upvotes: 10

Views: 7843

Answers (6)

Ben
Ben

Reputation: 1

There's a small issue with @ngryman's solution; if there is only a single url in the array of urls to load, the .done() function's arguments param will be a single dimensional array, rather than 2 dimensional. You'll need to check arguments before looping through, thus:

/**
 * Load scripts in parallel keeping execution order.
 * @param {array} An array of script urls. They will parsed in the order of the array.
 * @returns {$.Deferred}
 */
function getScripts(scripts) {
    var xhrs = scripts.map(function(url) {
        return $.ajax({
            url: url,
            dataType: 'text',
            cache: true
        });
    });

    return $.when.apply($, xhrs).done(function() {
        if (arguments.length == 3 && typeof arguments[0] == "string") {
            arguments = [arguments];
        }
        Array.prototype.forEach.call(arguments, function(res) {
            eval.call(this, res[0]);
        });
    });
}

Apologies for new answer, not enough rep to post as a comment on @ngryman's answer, but thought it might help someone out!

Upvotes: 0

ngryman
ngryman

Reputation: 7672

An enhanced version of @Jasper's solution:

  • using $.when to synchronize the calls.
  • using a global eval.

Here is the code:

/**
 * Load scripts in parallel keeping execution order.
 * @param {array} An array of script urls. They will parsed in the order of the array.
 * @returns {$.Deferred}
 */
function getScripts(scripts) {
    var xhrs = scripts.map(function(url) {
        return $.ajax({
            url: url,
            dataType: 'text',
            cache: true
        });
    });

    return $.when.apply($, xhrs).done(function() {
        Array.prototype.forEach.call(arguments, function(res) {
            eval.call(this, res[0]);
        });
    });
}

This gist: https://gist.github.com/ngryman/7309432.

Upvotes: 2

Felix Kling
Felix Kling

Reputation: 817128

You make make use of the fact the jqXhr object returned by $.getScript (and all other Ajax methods) implements the Promise interface and therefore provides .pipe() which can be used to chain deferred objects:

var deferred = new $.Deferred(),
    pipe = deferred;

$.each(scripts , function(i, val) {
     pipe = pipe.pipe(function() {
         return  $.getScript(val, function() {
             console.log('loaded '+ val);
         });
     });
});

deferred.resolve();

For more information, have a look at deferred objects and deferred.pipe.

Overall, using deferred objects gives you greater flexibility and might let you add error handlers more easily.

Upvotes: 10

Jasper
Jasper

Reputation: 76003

You can load each after the previous has finished loading by using the callback function of $.getScript() as a recursive function call.

//setup array of scripts and an index to keep track of where we are in the process
var scripts = ['script1.js','script2.js','script3.js'],
    index   = 0;

//setup a function that loads a single script
function load_script() {

    //make sure the current index is still a part of the array
    if (index < scripts.length) {

        //get the script at the current index
        $.getScript(scripts[index], function () {

            //once the script is loaded, increase the index and attempt to load the next script
            console.log('Loaded: ' + scripts[index]);
            index++;
            load_script();
        });
    }
}

What's occurring in your code is that the scripts are being requested at the same time and since they load asynchronously, they return and execute in random order.

Update

I haven't tested this, but if the scripts are hosted locally, then you could try to retrieve them in plain text, then store all of the code in variables until they are all loaded at which time you could evaluate the scripts in order:

var scripts   = ['script1.js','script2.js','script3.js'],

    //setup object to store results of AJAX requests
    responses = {};

//create function that evaluates each response in order
function eval_scripts() {
    for (var i = 0, len = scripts.length; i < len; i++) {
        eval(responses[scripts[i]]);
    }
}

$.each(scripts, function (index, value) {
    $.ajax({
        url      : scripts[index],

        //force the dataType to be `text` rather than `script`
        dataType : 'text',
        success  : function (textScript) {

            //add the response to the `responses` object
            responses[value] = textScript;

            //check if the `responses` object has the same length as the `scripts` array,
            //if so then evaluate the scripts
            if (responses.length === scripts.length) { eval_scripts(); }
        },
        error    : function (jqXHR, textStatus, errorThrown) { /*don't forget to handle errors*/ }
    });
});

Upvotes: 18

Austin Brunkhorst
Austin Brunkhorst

Reputation: 21130

Some scripts could vary in size, therefore it's unpredictable. Try something like this.

var scripts = ['script1.js','script2.js','script3.js'], ScriptIndex = 0;
function LoadScript(index){
    if(index >= scripts.length)
        return;

    $.getScript(scripts[index], function(){
        console.log('Loaded script '+ scripts[index]);
        LoadScript(++ScriptIndex);
    }
}

This should load each script after the last one has fully loaded. Make sure you start it off by calling the function LoadScript(0);

Upvotes: 1

Jay Blanchard
Jay Blanchard

Reputation: 34426

You can sort() the array -

var sorted = scripts.sort();

and then use sorted in your each function.

Upvotes: -1

Related Questions