Is it possible to call in a .js
file synchronously and then use it immediately afterward?
Asynchronous programming is slightly more complicated because the consequence of making a request is encapsulated in a function instead of following the request statement. But the realtime behavior that the user experiences can be significantly better because they will not see a sluggish server or sluggish network cause the browser to act as though it had crashed. Synchronous programming is disrespectful and should not be employed in applications which are used by people.
Douglas Crockford (YUI Blog)
Alright, buckle your seats, because it's going to be a bumpy ride. More and more people ask about loading scripts dynamically via javascript, it seems to be a hot topic.
The main reasons why this became so popular are:
About modularity: it is obvious that managing client-side dependencies should be handled right on the client-side. If a certain object, module or library is needed we just ask for it and load it dynamically.
Error handling: if a resource fails we still get the chance to block only the parts that depend on the affected script, or maybe even give it another try with some delay.
Performance has become a competitive edge between websites, it is now a search ranking factor. What dynamic scripts can do is mimic asynchronous behavior as opposed to the default blocking way of how browsers handle scripts. Scripts block other resources, scripts block further parsing of the HTML document, scripts block the UI. Now with dynamic script tags and its cross-browser alternatives you can do real asynchronous requests, and execute dependent code only when they are available. Your scripts will load in-parallel even with other resources and the rendering will be flawless.
The reason why some people stick to synchronous scripting is because they are used to it. They think it is the default way, it is the easier way, and some may even think it is the only way.
But the only thing we should care about when this needs to be decided concerning an applications's design is the end-user experience. And in this area asynchronous cannot be beaten. The user gets immediate responses (or say promises), and a promise is always better than nothing. A blank screen scares people. Developers shouldn't be lazy to enhance perceived performance.
And finally some words about the dirty side. What you should do in order to get it working across browsers:
I had the following problem(s) with the existing answers to this question (and variations of this question on other stackoverflow threads):
Or, slightly more accurately:
My final solution, which loads the script before returning, AND has all scripts properly accessible in the debugger (for Chrome at least) is as follows:
WARNING: The following code should PROBABLY be used only in 'development' mode. (For 'release' mode I recommend prepackaging and minification WITHOUT dynamic script loading, or at least without eval).
//Code User TODO: you must create and set your own 'noEval' variable
require = function require(inFileName)
{
var aRequest
,aScript
,aScriptSource
;
//setup the full relative filename
inFileName =
window.location.protocol + '//'
+ window.location.host + '/'
+ inFileName;
//synchronously get the code
aRequest = new XMLHttpRequest();
aRequest.open('GET', inFileName, false);
aRequest.send();
//set the returned script text while adding special comment to auto include in debugger source listing:
aScriptSource = aRequest.responseText + '\n////# sourceURL=' + inFileName + '\n';
if(noEval)//<== **TODO: Provide + set condition variable yourself!!!!**
{
//create a dom element to hold the code
aScript = document.createElement('script');
aScript.type = 'text/javascript';
//set the script tag text, including the debugger id at the end!!
aScript.text = aScriptSource;
//append the code to the dom
document.getElementsByTagName('body')[0].appendChild(aScript);
}
else
{
eval(aScriptSource);
}
};
Ironically, I have what you want, but want something closer to what you had.
I am loading things in dynamically and asynchronously, but with an load
callback like so (using dojo and xmlhtpprequest)
dojo.xhrGet({
url: 'getCode.php',
handleAs: "javascript",
content : {
module : 'my.js'
},
load: function() {
myFunc1('blarg');
},
error: function(errorMessage) {
console.error(errorMessage);
}
});
For a more detailed explanation, see here
The problem is that somewhere along the line the code gets evaled, and if there's anything wrong with your code, the console.error(errorMessage);
statement will indicate the line where eval()
is, not the actual error. This is SUCH a big problem that I am actually trying to convert back to <script>
statements (see here.
function include(file){
return new Promise(function(resolve, reject){
var script = document.createElement('script');
script.src = file;
script.type ='text/javascript';
script.defer = true;
document.getElementsByTagName('head').item(0).appendChild(script);
script.onload = function(){
resolve()
}
script.onerror = function(){
reject()
}
})
/*I HAVE MODIFIED THIS TO BE PROMISE-BASED
HOW TO USE THIS FUNCTION
include('js/somefile.js').then(function(){
console.log('loaded');
},function(){
console.log('not loaded');
})
*/
}
This is way late but for future reference to anyone who'd like to do this, you can use the following:
function require(file,callback){
var head=document.getElementsByTagName("head")[0];
var script=document.createElement('script');
script.src=file;
script.type='text/javascript';
//real browsers
script.onload=callback;
//Internet explorer
script.onreadystatechange = function() {
if (this.readyState == 'complete') {
callback();
}
}
head.appendChild(script);
}
I did a short blog post on it some time ago http://crlog.info/2011/10/06/dynamically-requireinclude-a-javascript-file-into-a-page-and-be-notified-when-its-loaded/
I am used to having multiple .js files on my web site that depend one on another. To load them and ensure that the dependencies are evaluated in the right order, I have written a function that loads all the files and then, once they are all received, eval()
them. The main drawback is that since this does not work with CDN. For such libraries (e.g., jQuery) it is better to include them statically. Note that inserting script nodes in the HTML dynamically won't guarantee that scripts are evaluated in the right order, at least not in Chrome (this was the major reason for writing this function).
function xhrs(reqs) {
var requests = [] , count = [] , callback ;
callback = function (r,c,i) {
return function () {
if ( this.readyState == 4 ) {
if (this.status != 200 ) {
r[i]['resp']="" ;
}
else {
r[i]['resp']= this.responseText ;
}
c[0] = c[0] - 1 ;
if ( c[0] == 0 ) {
for ( var j = 0 ; j < r.length ; j++ ) {
eval(r[j]['resp']) ;
}
}
}
}
} ;
if ( Object.prototype.toString.call( reqs ) === '[object Array]' ) {
requests.length = reqs.length ;
}
else {
requests.length = 1 ;
reqs = [].concat(reqs);
}
count[0] = requests.length ;
for ( var i = 0 ; i < requests.length ; i++ ) {
requests[i] = {} ;
requests[i]['xhr'] = new XMLHttpRequest () ;
requests[i]['xhr'].open('GET', reqs[i]) ;
requests[i]['xhr'].onreadystatechange = callback(requests,count,i) ;
requests[i]['xhr'].send(null);
}
}
I haven't figured out how to make references to the same value without creating an array (for count). Otherwise I think it is self-explanatory (when everything is loaded, eval()
every file in the order given, otherwise just store the response).
Usage example:
xhrs( [
root + '/global.js' ,
window.location.href + 'config.js' ,
root + '/js/lib/details.polyfill.min.js',
root + '/js/scripts/address.js' ,
root + '/js/scripts/tableofcontents.js'
]) ;