Reputation: 2275
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
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
Reputation: 7672
An enhanced version of @Jasper's solution:
$.when
to synchronize the calls.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
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
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.
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
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
Reputation: 34426
You can sort() the array -
var sorted = scripts.sort();
and then use sorted in your each function.
Upvotes: -1