Dynamic JavaScript loading synchronously

I am using a module template , one of the things I want to do is dynamically include an external JavaScript file, execute the file, and then use the functions / variables in the file in the return { } my module.

I canโ€™t figure out how to do this easily. Are there standard ways to execute a pseudo-synchronous external script load?

 function myModule() { var tag = document.createElement("script"); tag.type = "text/javascript"; tag.src = "http://some/script.js"; document.getElementsByTagName('head')[0].appendChild(tag); //something should go here to ensure file is loaded before return is executed return { external: externalVariable } } 
+48
javascript
May 21 '10 at 4:10
source share
15 answers

There is only one way to synchronously load and execute a script resource that uses synchronous XHR

This is an example of how to do this.

 // get some kind of XMLHttpRequest var xhrObj = createXMLHTTPObject(); // open and send a synchronous request xhrObj.open('GET', "script.js", false); xhrObj.send(''); // add the returned content to a newly created script tag var se = document.createElement('script'); se.type = "text/javascript"; se.text = xhrObj.responseText; document.getElementsByTagName('head')[0].appendChild(se); 

But you should not use synchronous requests at all, as this blocks everything else. But, as they say, there are, of course, scenarios where this is appropriate.

I would probably reorganize the containing function into an asynchronous template, but using the onload handler.

+50
May 21 '10 at 7:02 a.m.
source share

The answer is NOT .

Downloading a file synchronously does not match executing the file synchronously - this is what the OP requested.

The accepted answer loads the file synchronization, but no more than adds a script tag to the DOM. Just because appendChild () is back does not guarantee in any way that the script has completed execution and its members are initialized for use.

The only way (see the disclaimer) to solve the OP question is to synchronize the script loading on top of XHR, as indicated, then read as text and pass either to eval () or to a new Function () call, and wait for this to return. This is the only way to ensure that the script loads AND synchronously.

I do not comment on how reasonable it is to do this in terms of the user interface or security, but there are, of course, use cases that justify downloading and synchronizing.

Protest: If you are not using web workers, then just call loadScripts ();

+29
Feb 12 '14 at 2:36
source share

This is the code that I use to upload multiple files in my application.

 Utilities.require = function (file, callback) { callback = callback || function () {}; var filenode; var jsfile_extension = /(.js)$/i; var cssfile_extension = /(.css)$/i; if (jsfile_extension.test(file)) { filenode = document.createElement('script'); filenode.src = file; // IE filenode.onreadystatechange = function () { if (filenode.readyState === 'loaded' || filenode.readyState === 'complete') { filenode.onreadystatechange = null; callback(); } }; // others filenode.onload = function () { callback(); }; document.head.appendChild(filenode); } else if (cssfile_extension.test(file)) { filenode = document.createElement('link'); filenode.rel = 'stylesheet'; filenode.type = 'text/css'; filenode.href = file; document.head.appendChild(filenode); callback(); } else { console.log("Unknown file type to load.") } }; Utilities.requireFiles = function () { var index = 0; return function (files, callback) { index += 1; Utilities.require(files[index - 1], callBackCounter); function callBackCounter() { if (index === files.length) { index = 0; callback(); } else { Utilities.requireFiles(files, callback); } }; }; }(); 

And these utilities can be used

 Utilities.requireFiles(["url1", "url2",....], function(){ //Call the init function in the loaded file. }) 
+9
Jun 19 2018-12-12T00:
source share

The most Node.js-like implementation I could come up with was able to sync JS files and use them as objects / modules

 var scriptCache = []; var paths = []; function Import(path) { var index = 0; if((index = paths.indexOf(path)) != -1) //If we already imported this module { return scriptCache [index]; } var request, script, source; var fullPath = window.location.protocol + '//' + window.location.host + '/' + path; request = new XMLHttpRequest(); request.open('GET', fullPath, false); request.send(); source = request.responseText; var module = (function concealedEval() { eval(source); return exports; })(); scriptCache.push(module); paths.push(path); return module; } 

Source example ( addobjects.js ):

 function AddTwoObjects(a, b) { return a + b; } this.exports = AddTwoObjects; 

And use it as follows:

 var AddTwoObjects = Import('addobjects.js'); alert(AddTwoObjects(3, 4)); //7 //or even like this: alert(Import('addobjects.js')(3, 4)); //7 
+3
Aug 08 '16 at 2:36 on
source share

I had the following problem with existing answers to this question (and variations of this question in other stackoverflow threads):

  • No downloaded code has been debugged
  • Many of the solutions required that the callbacks know when the download was finished, and not really block, that is, I get runtime errors from directly invoking the loaded (i.e., loaded) code.

Or, a little more precisely:

  • None of the downloadable code was debugged (with the exception of the HTML script tag block, if and only if the solution added script elements to the house) and was never as separate visible scripts.) => Given how many scripts I have to load (and debug), this was unacceptable.
  • Solutions using the onreadystatechange or onload events failed to block, which was a big problem since the source code initially loaded dynamic scripts using 'require ([filename,' dojo / domReady ']); and I removed dojo.

My final solution, which loads the script before returning, AND has all the scripts correctly accessible in the debugger (at least for Chrome), looks like this:

WARNING: The following code should only be used in development mode. (for release mode, I recommend pre-packing and minimizing WITHOUT dynamically loading the script, 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); } }; 
+2
Feb 15 '14 at 8:34
source share
 var xhrObj = new XMLHttpRequest(); xhrObj.open('GET', '/filename.js', false); xhrObj.send(null); eval(xhrObj.responseText); 

If this is a cross-domain request, it will not work. In this case, you need to upload the requested file to your server or make a mirrored php that displays it, and require php.

With jquery (also works with cross-domain query):

 $.getScript('/filename.js',callbackFunction); 

callbackFunction will be called synchronously.

To download additional scripts, see this thread.

+2
Jul 16 '14 at 10:07
source share

If you need to load an arbitrary number of scripts and continue to work only after the last one, and you cannot use XHR (for example, due to CORS restrictions), you can do the following. It is not synchronous, but allows you to call back exactly when loading the last file:

 // Load <script> elements for all uris // Invoke the whenDone callback function after the last URI has loaded function loadScripts(uris,whenDone){ if (!uris.length) whenDone && whenDone(); else{ for (var wait=[],i=uris.length;i--;){ var tag = document.createElement('script'); tag.type = 'text/javascript'; tag.src = uris[i]; if (whenDone){ wait.push(tag) tag.onload = maybeDone; tag.onreadystatechange = maybeDone; // For IE8- } document.body.appendChild(tag); } } function maybeDone(){ if (this.readyState===undefined || this.readyState==='complete'){ // Pull the tags out based on the actual element in case IE ever // intermingles the onload and onreadystatechange handlers for the same // script block before notifying for another one. for (var i=wait.length;i--;) if (wait[i]==this) wait.splice(i,1); if (!wait.length) whenDone(); } } } 

Change Updated to work with IE7, IE8 and IE9 (in quirks mode). These versions of IE do not fire the onload , but do so for onreadystatechange . IE9 in standard mode runs both (with onreadystatechange for all scripts that run before onload for any).

Based on this page, there may be a small chance that older versions of IE will never send an onreadystatechange event with readyState=='complete' ; if so (I could not reproduce this problem) then the above script will fail and your callback will never be called.

+1
Jun 20 '12 at 16:12
source share

You cannot and should not perform server operations synchronously for obvious reasons. However, you can have an event handler telling you when the script loads:

 tag.onreadystatechange = function() { if (this.readyState == 'complete' || this.readyState == 'loaded') this.onload({ target: this }); }; tag.onload = function(load) {/*init code here*/} 

onreadystatechange Delegation - from memory - a workaround for IE that has mixed support for onload .

0
May 21 '10 at 4:20
source share

same as Shawn's answer, but instead of creating a script tag just evaluate it. this ensures that the code is truly ready to use.

0
May 21 '12 at 19:23
source share

My strategy, classic example when loading jQuery UI, hope this helps you

 ( function( tools, libs ){ // Iterator var require = function( scripts, onEnd ){ onEnd = onEnd || function(){}; if( !scripts || scripts.length < 1 )return onEnd(); var src = scripts.splice( 0, 1), script = document.createElement( "script" ); script.setAttribute( "src", src ); tools.addEvent( "load", script, function(){ require( scripts, onEnd ); } ); document.getElementsByTagName( "head" )[ 0 ].appendChild( script ); }; // Install all scripts with a copy of scripts require( libs.slice(), function(){ alert( "Enjoy :)" ); } ); // Timeout information var ti = setTimeout( function(){ if( !window.jQuery || !window.jQuery.ui )alert( "Timeout !" ); clearTimeout( ti ); }, 5000 ); } )( { // Tools addEvent : function( evnt, elem, func ){ try{ if( elem.addEventListener ){ elem.addEventListener( evnt, func, false ); }else if( elem.attachEvent ){ var r = elem.attachEvent( "on" + evnt, func ); } return true; }catch( e ){ return false; } } }, [ // Scripts "https://cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0-alpha1/jquery.min.js", "https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js" ] ); 
0
Aug 26 '15 at 11:01
source share

When using Angular, you can take advantage of the fact that each provider is created when other services are created. You can combine this fact with xhr and eval (), as pointed out by @Neil. The code will look like this:

 app.provider('SomeScriptSyncLoader', function() { var resourceUrl = 'http://some/script.js'; var dummy = {}; this.$get = function() { var q = jQuery.ajax({ type: 'GET', url: resourceUrl, cache: false, async: false }); if (q.status === 200) { eval(q.responseText); // execute some script synchronously as inline script - eval forces sync processing } return dummy; }; }); 

To force a provider to be initialized, you need to enter it in at least one other directive / service. Preferably, it will be a service that uses code downloaded using a script.

 app.directive('myDirective', ['SomeScriptSyncLoader', function(someScriptSyncLoader) { return { restrict: 'E', link: function(scope, element, attrs) { // some ode }, template: "this is my template" }; }]); 
0
Jan 26 '16 at 12:36
source share

I know this is an old question, but maybe someone else will read this and find it useful! Just created a new component uses ES6 for dynamic synchronous script loading. Project details and source code are on GitHub https://github.com/amgadfahmi/scripty

0
Oct. 20 '16 at 2:49 on
source share

Maybe I'm late to answer this question.

My current solution is to add <script> tags recursively so that adding the next script is in the callback of its predecessor. It assumes that each function contains one function, and this function matches the file name (minus the extension). This may not be the best way to do something, but it works fine.

Code to review

Directory directory structure:

 - directory ---- index.html ---- bundle.js ---- test_module/ -------- a.js -------- b.js -------- log_num.js -------- many_parameters.js 

index.html

 <head> <script src="bundle.js"></script> </head> 

bundle.js

 // Give JS arrays the .empty() function prototype if (!Array.prototype.empty){ Array.prototype.empty = function(){ return this.length == 0; }; }; function bundle(module_object, list_of_files, directory="") { if (!list_of_files.empty()) { var current_file = list_of_files.pop() var [function_name, extension] = current_file.split(".") var new_script = document.createElement("script") document.head.appendChild(new_script) new_script.src = directory + current_file new_script.onload = function() { module_object[function_name] = eval(function_name) bundle(module_object, list_of_files, directory) /* nullify the function in the global namespace as - assumed - last reference to this function garbage collection will remove it. Thus modules assembled by this function - bundle(obj, files, dir) - must be called FIRST, else one risks overwritting a funciton in the global namespace and then deleting it */ eval(function_name + "= undefined") } } } var test_module = {} bundle(test_module, ["a.js", "b.js", "log_num.js", "many_parameters.js"], "test_module/") 

a.js

 function a() { console.log("a") } 

b.js

 function b() { console.log("b") } 

log_num.js

 // it works with parameters too function log_num(num) { console.log(num) } 

many_parameters.js

 function many_parameters(a, b, c) { var calc = a - b * c console.log(calc) } 
0
May 09 '17 at 18:32
source share

In fact, there is a way to load a list of scripts and execute them synchronously. You need to insert each script tag in the DOM, explicitly setting the async attribute to false:

 script.async = false; 

The scripts that were injected into the DOM run asynchronously by default, so you need to set the async attribute to false manually to get around this.

Example

 <script> (function() { var scriptNames = [ "https://code.jquery.com/jquery.min.js", "example.js" ]; for (var i = 0; i < scriptNames.length; i++) { var script = document.createElement('script'); script.src = scriptNames[i]; script.async = false; // This is required for synchronous execution document.head.appendChild(script); } // jquery.min.js and example.js will be run in order and synchronously })(); </script> <!-- Gotcha: these two script tags may still be run before `jquery.min.js` and `example.js` --> <script src="example2.js"></script> <script>/* ... */<script> 

References

0
Jul 11 '17 at 10:51 on
source share

I am using jquery loading method applied to div element. something like

 <div id="js"> <!-- script will be inserted here --> </div> ... $("#js").load("path", function() { alert("callback!" }); 

You can load scripts several times, and every time one script completely replaces the one you downloaded earlier

-one
May 21 '10 at 7:07 a.m.
source share



All Articles