jQuery: load scripts in order

2019-02-03 12:02发布

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?

6条回答
迷人小祖宗
2楼-- · 2019-02-03 12:27

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!

查看更多
SAY GOODBYE
3楼-- · 2019-02-03 12:28

You can sort() the array -

var sorted = scripts.sort();

and then use sorted in your each function.

查看更多
我想做一个坏孩纸
4楼-- · 2019-02-03 12:31

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*/ }
    });
});
查看更多
啃猪蹄的小仙女
5楼-- · 2019-02-03 12:33

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.

查看更多
放荡不羁爱自由
6楼-- · 2019-02-03 12:37

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.

查看更多
看我几分像从前
7楼-- · 2019-02-03 12:38

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

查看更多
登录 后发表回答