jQuery.getScript alternative in native JavaScript

JavascriptJqueryGetscript

Javascript Problem Overview


I'm trying to load JS scripts dynamically, but using jQuery is not an option.

I checked jQuery source to see how getScript was implemented so that I could use that approach to load scripts using native JS. However, getScript only calls jQuery.get()

and I haven't been able to find where the get method is implemented.

So my question is,

What's a reliable way to implement my own getScript method using native JavaScript?

Thanks!

Javascript Solutions


Solution 1 - Javascript

Here's a jQuery getScript alternative with callback functionality:

function getScript(source, callback) {
	var script = document.createElement('script');
	var prior = document.getElementsByTagName('script')[0];
	script.async = 1;
	
	script.onload = script.onreadystatechange = function( _, isAbort ) {
		if(isAbort || !script.readyState || /loaded|complete/.test(script.readyState) ) {
			script.onload = script.onreadystatechange = null;
			script = undefined;

			if(!isAbort && callback) setTimeout(callback, 0);
		}
	};
	
	script.src = source;
	prior.parentNode.insertBefore(script, prior);
}

Solution 2 - Javascript

You can fetch scripts like this:

(function(document, tag) {
    var scriptTag = document.createElement(tag), // create a script tag
        firstScriptTag = document.getElementsByTagName(tag)[0]; // find the first script tag in the document
    scriptTag.src = 'your-script.js'; // set the source of the script to your script
    firstScriptTag.parentNode.insertBefore(scriptTag, firstScriptTag); // append the script to the DOM
}(document, 'script'));

Solution 3 - Javascript

use this

var js_script = document.createElement('script');
js_script.type = "text/javascript";
js_script.src = "http://www.example.com/script.js";
js_script.async = true;
document.getElementsByTagName('head')[0].appendChild(js_script);

Solution 4 - Javascript

Firstly, Thanks for @Mahn's answer. I rewrote his solution in ES6 and promise, in case someone need it, I will just paste my code here:

const loadScript = (source, beforeEl, async = true, defer = true) => {
  return new Promise((resolve, reject) => {
    let script = document.createElement('script');
    const prior = beforeEl || document.getElementsByTagName('script')[0];

    script.async = async;
    script.defer = defer;

    function onloadHander(_, isAbort) {
      if (isAbort || !script.readyState || /loaded|complete/.test(script.readyState)) {
        script.onload = null;
        script.onreadystatechange = null;
        script = undefined;

        if (isAbort) { reject(); } else { resolve(); }
      }
    }

    script.onload = onloadHander;
    script.onreadystatechange = onloadHander;

    script.src = source;
    prior.parentNode.insertBefore(script, prior);
  });
}

Usage:

const scriptUrl = 'https://www.google.com/recaptcha/api.js?onload=onRecaptchaLoad&render=explicit';
loadScript(scriptUrl).then(() => {
  console.log('script loaded');
}, () => {
  console.log('fail to load script');
});

and code is eslinted.

Solution 5 - Javascript

This polishes up previous ES6 solutions and will work in all modern browsers

Load and Get Script as a Promise
const getScript = url => new Promise((resolve, reject) => {
  const script = document.createElement('script')
  script.src = url
  script.async = true

  script.onerror = reject

  script.onload = script.onreadystatechange = function() {
    const loadState = this.readyState

    if (loadState && loadState !== 'loaded' && loadState !== 'complete') return

    script.onload = script.onreadystatechange = null

    resolve()
  }

  document.head.appendChild(script)
})
Usage
getScript('https://dummyjs.com/js')
.then(() => {
  console.log('Loaded', dummy.text())
})
.catch(() => {
  console.error('Could not load script')
})

Also works for JSONP endpoints

const callbackName = `_${Date.now()}`
getScript('http://example.com/jsonp?callback=' + callbackName)
.then(() => {
  const data = window[callbackName];

  console.log('Loaded', data)
})

Also, please be careful with some of the AJAX solutions listed as they are bound to the CORS policy in modern browsers https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS

Solution 6 - Javascript

There are some good solutions here but many are outdated. There is a good one by @Mahn but as stated in a comment it is not exactly a replacement for $.getScript() as the callback does not receive data. I had already written my own function for a replacement for $.get() and landed here when I need it to work for a script. I was able to use @Mahn's solution and modify it a bit along with my current $.get() replacement and come up with something that works well and is simple to implement.

function pullScript(url, callback){
	pull(url, function loadReturn(data, status, xhr){
		//If call returned with a good status
		if(status == 200){
			var script = document.createElement('script');
			//Instead of setting .src set .innerHTML
			script.innerHTML = data;
			document.querySelector('head').appendChild(script);
		}
		if(typeof callback != 'undefined'){
			//If callback was given skip an execution frame and run callback passing relevant arguments
			setTimeout(function runCallback(){callback(data, status, xhr)}, 0);
		}
	});
}

function pull(url, callback, method = 'GET', async = true) {
	//Make sure we have a good method to run
	method = method.toUpperCase();
	if(!(method === 'GET'   ||   method === 'POST'   ||  method === 'HEAD')){
		throw new Error('method must either be GET, POST, or HEAD');
	}
	//Setup our request
	var xhr = new XMLHttpRequest();
	xhr.onreadystatechange = function() {
		if (xhr.readyState == XMLHttpRequest.DONE) {   // XMLHttpRequest.DONE == 4
			//Once the request has completed fire the callback with relevant arguments
			//you should handle in your callback if it was successful or not
			callback(xhr.responseText, xhr.status, xhr);
		}
	};
	//Open and send request
	xhr.open(method, url, async);
	xhr.send();
}

Now we have a replacement for $.get() and $.getScript() that work just as simply:

pullScript(file1, function(data, status, xhr){
	console.log(data);
	console.log(status);
	console.log(xhr);
});

pullScript(file2);

pull(file3, function loadReturn(data, status){
	if(status == 200){
		document.querySelector('#content').innerHTML = data;
	}
}

Solution 7 - Javascript

Mozilla Developer Network provides an example that works asynchronously and does not use 'onreadystatechange' (from @ShaneX's answer) that is not really present in a HTMLScriptTag:

function loadError(oError) {
  throw new URIError("The script " + oError.target.src + " didn't load correctly.");
}

function prefixScript(url, onloadFunction) {
  var newScript = document.createElement("script");
  newScript.onerror = loadError;
  if (onloadFunction) { newScript.onload = onloadFunction; }
  document.currentScript.parentNode.insertBefore(newScript, document.currentScript);
  newScript.src = url;
}

Sample usage:

prefixScript("myScript1.js");
prefixScript("myScript2.js", function () { alert("The script \"myScript2.js\" has been correctly loaded."); });

But @Agamemnus' comment should be considered: The script might not be fully loaded when onloadFunction is called. A timer could be used setTimeout(func, 0) to let the event loop finalize the added script to the document. The event loop finally calls the function behind the timer and the script should be ready to use at this point.

However, maybe one should consider returning a Promise instead of providing two functions for exception & success handling, that would be the ES6 way. This would also render the need for a timer unnecessary, because Promises are handled by the event loop - becuase by the time the Promise is handled, the script was already finalized by the event loop.

Implementing Mozilla's method including Promises, the final code looks like this:

function loadScript(url)
{
  return new Promise(function(resolve, reject)
  {
    let newScript = document.createElement("script");
    newScript.onerror = reject;
    newScript.onload = resolve;
    document.currentScript.parentNode.insertBefore(newScript, document.currentScript);
    newScript.src = url;
  });
}

loadScript("test.js").then(() => { FunctionFromExportedScript(); }).catch(() => { console.log("rejected!"); });

Solution 8 - Javascript

window.addEventListener('DOMContentLoaded',
       function() {
           var head = document.getElementsByTagName('HEAD')[0];
           var script = document.createElement('script');
           script.src = "/Content/index.js";
           head.appendChild(script);
       });

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
QuestionILikeTacosView Question on Stackoverflow
Solution 1 - JavascriptMahnView Answer on Stackoverflow
Solution 2 - JavascriptMathleticsView Answer on Stackoverflow
Solution 3 - JavascriptRohit AgrawalView Answer on Stackoverflow
Solution 4 - JavascriptAXEView Answer on Stackoverflow
Solution 5 - Javascriptpaulcol.View Answer on Stackoverflow
Solution 6 - JavascriptXandorView Answer on Stackoverflow
Solution 7 - Javascriptm.w.View Answer on Stackoverflow
Solution 8 - JavascriptAmr A. ElabdView Answer on Stackoverflow