Inject a script tag with remote src and wait for it to execute

Javascript

Javascript Problem Overview


How can I inject a <script src="https://remote.com/"></script> element into my page, wait for it to execute, and then use functions that it defines?

FYI: In my case, the script will do some credit card processing in rare cases, so I don't want to include it always. I want to include it quickly when the user opens up a change-credit-card-options dialog, and then send it the new credit card options.

Edit for additional detail: I do not have access to the remote script.

Javascript Solutions


Solution 1 - Javascript

You could use Google Analytics or Facebook's method:

(function(d, script) {
    script = d.createElement('script');
    script.type = 'text/javascript';
    script.async = true;
    script.onload = function(){
        // remote script has loaded
    };
    script.src = 'http://www.google-analytics.com/ga.js';
    d.getElementsByTagName('head')[0].appendChild(script);
}(document));

UPDATE:

Below is the new Facebook method; it relies on an existing script tag instead of <head>:

(function(d, s, id){
    var js, fjs = d.getElementsByTagName(s)[0];
    if (d.getElementById(id)){ return; }
    js = d.createElement(s); js.id = id;
    js.onload = function(){
        // remote script has loaded
    };
    js.src = "//connect.facebook.net/en_US/sdk.js";
    fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));
  • Replace facebook-jssdk with your unique script identifier to avoid it being appended more than once.
  • Replace the script's url with your own.

Solution 2 - Javascript

Same method using event listeners and ES2015 constructs:

function injectScript(src) {
    return new Promise((resolve, reject) => {
        const script = document.createElement('script');
        script.src = src;
        script.addEventListener('load', resolve);
        script.addEventListener('error', e => reject(e.error));
        document.head.appendChild(script);
    });
}

injectScript('https://example.com/script.js')
    .then(() => {
        console.log('Script loaded!');
    }).catch(error => {
        console.error(error);
    });

Solution 3 - Javascript

This is one way to dynamically load and execute a list of scripts synchronously. You need to insert each script tag into the DOM, explicitly setting its async attribute to false:

script.async = false;

Scripts that have been injected into the DOM are executed asynchronously by default, so you have to set the async attribute to false manually to work 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

Solution 4 - Javascript

Dynamic import()

Using dynamic import, you can now load modules and wait for them to excute, as simply as this:

import("http://example.com/module.js").then(function(module) {
  alert("module ready");
});

If the module has already been loaded and executed, it won't get loaded and executed again, but the promise returned by import will still resolve.

Note that the file is loaded as a module, not as just a script. Modules are executed in strict mode, and they are loaded in module scope, which means variables are not automatically made global the way they are in normally loaded scripts. Use the export keyword in a module to share a variable with other modules or scripts.

References:

Solution 5 - Javascript

something like this should do the trick:

(function() {
    // Create a new script node
	var script = document.createElement("script");
	script.type = "text/javascript";
	script.onload = function() {
		// Cleanup onload handler
		script.onload = null;
	
		// do stuff with the loaded script!
	
	}

	// Add the script to the DOM
	(document.getElementsByTagName( "head" )[ 0 ]).appendChild( script );

	// Set the `src` to begin transport
	script.src = "https://remote.com/";
})();

hope that helps! cheers.

Solution 6 - Javascript

Create a loader

You can inject the script in an orderly manner in a loader.

Beware that the execution of the dynamically loaded scripts usually comes after statically loaded scripts (i.e.<script src="My_script.js"></script>) (the order of injection in the DOM does not guarantee the opposite):

e.g., loader.js:

function appendScript(url){
   let script = document.createElement("script");
   script.src = url;
   script.async = false //IMPORTANT
   /*Node Insertion Point*/.appendChild(script);
}
appendScript("my_script1.js");
appendScript("my_script2.js");

my_script1.js will effectively be executed before my_script2.js, (helpful if dependencies of my_script2.js are in my_script1.js)

Note it's important to have script.async = false because dynamically loaded scripts has async = true by default, async does not assure you the order of loading.

Solution 7 - Javascript

Here is my adapted version, based on the answer of Frank:

static async importScript(src, expressionToEvaluateAndReturn){

		return new Promise((resolve, reject) => {
			const script = document.createElement('script');
			script.async = true;
			script.src = src;
			script.addEventListener('load', (event)=>{
				if(expressionToEvaluateAndReturn){
					try{
						let result = eval(expressionToEvaluateAndReturn);
						resolve(result);
					} catch(error){
						reject(error);
					}
					
				} else {
					resolve();
				}
			});
			script.addEventListener('error', () => reject('Error loading script "' + src + '"'));
			script.addEventListener('abort', () => reject('Script loading aborted for "' + src + '"'));
			document.head.appendChild(script);
		});    
		
	}	

Example usage:

let d3 = await importScript('/bower_components/d3/d3.min.js','d3')
                    .catch(error => {
                        console.log(error);
                        throw error;
                    });

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionRiley LarkView Question on Stackoverflow
Solution 1 - JavascriptPierreView Answer on Stackoverflow
Solution 2 - JavascriptFrank GambinoView Answer on Stackoverflow
Solution 3 - JavascriptFlimmView Answer on Stackoverflow
Solution 4 - JavascriptFlimmView Answer on Stackoverflow
Solution 5 - JavascriptkeeganwatkinsView Answer on Stackoverflow
Solution 6 - JavascriptSheedView Answer on Stackoverflow
Solution 7 - JavascriptStefanView Answer on Stackoverflow