Catch script tags 404 - javascript

Is it possible to catch <script type="text/javascript" src=".."> 404 with JavaScript so that I could correct src?
window.onerror doesn't seem to catch these.
Important note! This script is dynamically added into body by 3rd-party library, so I have no control over it.
It would be so nice if I could detect the script insertion or src setting and update it with correct src like I could do with image but I think it's not possible with script element?
var NativeImage = window.Image;
class MyImage {
constructor (w, h) {
var nativeImage = new NativeImage(w, h);
var handler = {
set: function (obj, prop, value) {
if (prop === 'src') {
return nativeImage[prop] = '/correct/image/path.jpg';
}
return nativeImage[prop] = value;
},
get: function (target, prop) {
return target[prop];
}
};
return new Proxy(nativeImage, handler);
}
}
window.Image = MyImage;

You can detect the script's insertion with MutationObserver and add an error listener to the tag:
// Your code (run this before the external script)
new MutationObserver((mutations, observer) => {
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
// Add additional checks here if needed
// to identify if the script is the one added by the library
if (node.nodeType === 1 && node.matches('script')) {
node.addEventListener('error', () => {
console.log('Script could not be loaded');
});
// Remove the observer, since its purpose is fulfilled
observer.disconnect();
return;
}
}
}
})
// Watch for elements that are added as children to document.body:
.observe(document.body, { childList: true });
<script>
// External script:
window.addEventListener('DOMContentLoaded', () => {
const script = document.createElement('script');
script.src = 'doesntexist';
document.body.appendChild(script);
});
</script>

You can check the HTTP Status of your script src by sending a XMLHttpRequest:
HTTP Status Code from URL in Javascript
function getStatus(url) {
var request = new XMLHttpRequest();
request.onreadystatechange = function() {
if (request.readyState === 4){
return request.status;
};
request.open("GET", url, true);
request.send();
}

Related

Intercept XHR and change request headers and url before send in JavaScript

I want to intercept all XHR requests being sent, and change their URL and headers before the request gets sent.
Found this similar question but there are no answers there.
I tried hooking XMLHttpRequest.prototype.open, But it only gives me access to the response:
(function () {
var origOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function () {
console.log(arguments); // prints method ("GET"), URL
console.log(this); // prints response, responseText, responseURL, status, statusText, and onXXX handlers
origOpen.apply(this, arguments);
};
})();
Also tried hooking XMLHttpRequest.prototype.setRequestHeader, but it only gives me access to each header value being set, one by one, and I can't associate it to the URL of the request:
(function () {
var origSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
XMLHttpRequest.prototype.setRequestHeader = function (header, value) {
console.log("header", header);
console.log("value", value);
origSetRequestHeader.apply(this, arguments);
};
})();
I managed to hook XMLHttpRequest.prototype.send to set a custom header, but since I want to change an existing header key, it appends my new value instead of replacing the existing one. Other people encountered the same problem: 1, 2:
(function () {
var origSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function () {
arguments[1] = myNewUrl; // arguments[1] holds the URL
this.setRequestHeader('existingHeaderKey', newHeaderValue)
origSend.apply(this, arguments);
};
})();
How can I accomplish this?
The XMLHttpRequest(xhr) interface exposes a very few things. So there is limitation on what you can intercept.
However, we can wrap the xhr objects in Proxy and collect data until send is called. And before sending the request we modify the data at one spot.
const OriginalXHR = XMLHttpRequest;
// wrap the XMLHttpRequest
XMLHttpRequest = function() {
return new Proxy(new OriginalXHR(), {
open(method, url, async, username = null, password = null) {
lg('open');
// collect URL and HTTP method
this.modMethod = method;
this.modUrl = url;
this.open(...arguments);
},
setRequestHeader(name, value) {
lg('set header');
if (!this.modReqHeaders) {
this.modReqHeaders = {};
}
// collect headers
this.modReqHeaders[name] = value;
// do NOT set headers here. Hold back!
// this.setRequestHeader(name, value);
},
send(body = null) {
lg('processing request...');
// do the final processing
// ...
// don't forget to set headers
for (const [name, value] of Object.entries(this.modReqHeaders)) {
this.setRequestHeader(name, value);
}
lg('sending request =>' +
'\n\t\tmethod: \t' + this.modMethod +
'\n\t\turl:\t\t' + this.modUrl +
'\n\t\theaders:\t' + JSON.stringify(this.modReqHeaders));
this.send(body);
},
get(xhr, key) {
if (!key in xhr) return undefined;
let value = xhr[key];
if (typeof value === "function") {
// if wrapped, use the function in proxy
value = this[key] || value;
return (...args) => value.apply(xhr, args);
} else {
//return properties
return value;
}
},
set(xhr, key, value) {
if (key in xhr) {
xhr[key] = value;
}
return value;
}
});
}
console.warn('XMLHttpRequest has been patched!\n XMLHttpRequest: ', XMLHttpRequest);
let url = 'https://baconipsum.com/api/?type=all-meat&sentences=1&start-with-lorem=1';
function getData() {
console.log('fetching lorem ipsum');
let xhr = new XMLHttpRequest();
xhr.responseType = 'json';
xhr.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
document.getElementById("demo").innerText = this.response[0];
}
};
xhr.open("GET", url, true);
xhr.setRequestHeader('Referer', 'www.google.com');
xhr.setRequestHeader('Accept-Encoding', 'x-compress; x-zip')
xhr.setRequestHeader('Accept-Language', 'de-US,en;q=0.5');
xhr.send();
}
//fancy logging, looks good in dark mode
function lg(msg) {
console.log('%c\t Proxy: ' + msg, 'background: #222; color: #bada55');
}
#demo {
min-height: 100px;
background-color: wheat;
}
<button onclick="getData()">Get data</button>
<div id="demo"></div>
<p>Note: look in the Developer Console for debug logs</p>
You can wrap remaining xhr methods or attributes in the proxy handler as per your requirement.
This may not be as good as service workers. But service workers have following drawbacks:
A service worker is run in a worker context: it therefore has no DOM access, and runs on a different thread to the main JavaScript that powers your app, so it is non-blocking. It is designed to be fully async; as a consequence, APIs such as synchronous XHR and Web Storage can't be used inside a service worker.
Service workers only run over HTTPS, for security reasons. Having modified network requests, wide open to man in the middle attacks would be really bad. In Firefox, Service Worker APIs are also hidden and cannot be used when the user is in private browsing mode.ref

Function return from XMLHttpRequest [duplicate]

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!
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);
}
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'));
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);
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.
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
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;
}
}
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!"); });
window.addEventListener('DOMContentLoaded',
function() {
var head = document.getElementsByTagName('HEAD')[0];
var script = document.createElement('script');
script.src = "/Content/index.js";
head.appendChild(script);
});
Here's a version that preserves the accept and x-requested-with headers, like jquery getScript:
function pullScript(url, callback){
pull(url, function loadReturn(data, status, xhr){
if(status === 200){
var script = document.createElement('script');
script.innerHTML = data; // Instead of setting .src set .innerHTML
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) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState === XMLHttpRequest.DONE) {
callback(xhr.responseText, xhr.status, xhr);
}
};
xhr.open('GET', url, true);
xhr.setRequestHeader('accept', '*/*;q=0.5, text/javascript, application/javascript, application/ecmascript, application/x-ecmascript');
xhr.setRequestHeader('x-requested-with', 'XMLHttpRequest');
xhr.send();
}
pullScript(URL);

Use HTTP-on-modify-request to redirect URL

I am building an add-on for Firefox that redirect request to a new URL if the URL match some conditions. I've tried this, and it does not work.
I register an observer on HTTP-on-modify-request to process the URL, if the URL match my condition, I will redirect to a new URL.
Here is my code:
var Cc = Components.classes;
var Ci = Components.interfaces;
var Cr = Components.results;
var newUrl = "https://google.com";
function isInBlacklist(url) {
// here will be somemore condition, I just use youtube.com to test
if (url.indexOf('youtube.com') != -1) {
return true;
}
return false;
}
exports.main = function(options,callbacks) {
// Create observer
httpRequestObserver =
{
observe: function (subject, topic, data) {
if (topic == "http-on-modify-request") {
var httpChannel = subject.QueryInterface(Ci.nsIHttpChannel);
var uri = httpChannel.URI;
var domainLoc = uri.host;
if (isInBlacklist(domainLoc) === true) {
httpChannel.cancel(Cr.NS_BINDING_ABORTED);
var gBrowser = utils.getMostRecentBrowserWindow().gBrowser;
var domWin = channel.notificationCallbacks.getInterface(Ci.nsIDOMWindow);
var browser = gBrowser.getBrowserForDocument(domWin.top.document);
browser.loadURI(newUrl);
}
}
},
register: function () {
var observerService = Cc["#mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
observerService.addObserver(this, "http-on-modify-request", false);
},
unregister: function () {
var observerService = Cc["#mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
observerService.removeObserver(this, "http-on-modify-request");
}
};
//register observer
httpRequestObserver.register();
};
exports.onUnload = function(reason) {
httpRequestObserver.unregister();
};
I am new to Firefox add-on development.
You can redirect a channel by calling nsIHttpChannel.redirectTo.
This is not possible once the channel is opened, but in http-on-modify-request it will work.
So in your code, you can do something like:
Cu.import("resource://gre/modules/Services.jsm");
// ...
if (condition) {
httpChannel.redirectTo(
Services.io.newURI("http://example.org/", null, null));
}
It looks like you might be using the Add-on SDK. In that case, read up on Using Chrome Authority.
You could simply do a
httpChannel.URI.spec = newUrl;
instead of
httpChannel.cancel(Cr.NS_BINDING_ABORTED);
...
browser.loadURI(newUrl);
Not sure how 'safe' it would be in your case, since I'm not exactly sure how other headers in the request (e.g. Cookie) would be manipulated when you change the URL to point to an entirely different domain at this stage.

When I call terminate on my web worker what state is it in?

I have a web worker that I'm using to poll for information.
Here is the code for starting and stopping the web worker:
var eftWorker = undefined;
that.StartWorker = function () {
if (eftWorker === undefined) {
eftWorker = new Worker('../scripts/ETEL.EftWorker.js');
eftWorker.addEventListener('message', function(e) {
EftWorkerResponseHandler(e.data);
}, false);
eftWorker.addEventListener('error', function(e) {
EftWorkerErrorResponseHandler(e);
}, false);
}
eftWorker.postMessage({ cmd: eftCmdStart });
};
that.StopWorker = function () {
if (eftWorker !== undefined) {
eftWorker.postMessage({ cmd: eftCmdStop });
eftWorker.terminate();
}
eftWorker = undefined;
};
When I call terminate on the worker, because the worker is polling there seems to be a backlog of unprocessed postmessage events.
I'm setting the web worker to "undefined" on initialisation of the containing view and on termination of the web worker. I believe because of the latter, those unprocessed events are shown as ABORT_ERRORs.
Is there an intermediate state that I can use to test the existence of the web worker so that I can allow it to process the outstanding events and therefore avoid the errors?
Or is there a different approach that I might use to avoid the accumulation of errors after terminate is called?
Here is my solution to the problem.
I'm recording the state of the worker in a separate variable in order that I can keep it alive to handle the outstanding messages that are causing the errors.
Also I'm trapping and discarding any errors generated inside the worker itself.
var eftWorker = undefined;
var eftWorkerState = undefined;
var workerStateStarted = 'started';
var workerStateStopped = 'stopped';
var StartWorker = function () {
if (eftWorker === undefined | eftWorkerState !== workerStateStarted) {
eftWorker = new Worker('/scripts/ETEL.EftWorker.js');
eftWorker.addEventListener('message', function(e) {
EftWorkerResponseHandler(e.data);
}, false);
eftWorker.addEventListener('error', function (e) {
EftWorkerErrorResponseHandler(e);
}, false);
}
eftWorker.postMessage({ cmd: eftCmdStart });
eftWorkerState = workerStateStarted;
};
that.StopWorker = function () {
if (eftWorker !== undefined) {
eftWorker.postMessage({ cmd: eftCmdStop });
eftWorker.terminate();
}
eftWorkerState = workerStateStopped;
//eftWorker = undefined;
};
var EftWorkerResponseHandler = function (msg) {
try {
if (eftWorkerState === workerStateStarted) {
if (msg != '' && msg !== undefined) {
var parser = new DOMParser();
var xmlDoc = parser.parseFromString(msg, 'text/xml');
var json = $.xmlToJSON(xmlDoc);
AlterPaymentUIAccordingToEftWorkerStatus(json);
}
}
} catch (exception) { }
};
And here's the code from the worker responsible for sending back the status messages.
EftSendGetRequest = function(passedUrl) {
if (xmlHttpReq === undefined) {
xmlHttpReq = new XMLHttpRequest();
}
try {
xmlHttpReq.open("GET", passedUrl, false);
xmlHttpReq.send();
} catch (e) { }
return xmlHttpReq.responseText;
};
This is old but I was looking at it while searching for a different answer..
Why not let the worker handle its own state and termination? The original question asks how to let the worker finish outstanding requests. So let it finish its requests and indicate when it is done.
If the javascript was something like this:
var eftWorker = undefined;
var StartWorker = function () {
if (eftWorker === undefined) {
eftWorker = new Worker('/scripts/ETEL.EftWorker.js');
eftWorker.addEventListener('message', function(e) {
EftWorkerResponseHandler(e.data);
}, false);
eftWorker.addEventListener('error', function (e) {
EftWorkerErrorResponseHandler(e);
}, false);
// i'm assuming we don't want to trigger start multiple times,
// so this is moved inside the IF.
eftWorker.postMessage({ cmd: eftCmdStart });
}
};
that.StopWorker = function () {
if (eftWorker !== undefined) {
eftWorker.postMessage({ cmd: eftCmdStop });
}
};
var EftWorkerResponseHandler = function (msg) {
try {
if (msg && msg === 'readyToTerminate') {
eftWorker.terminate();
eftWorker = undefined;
} else {
// handle other situations.
}
} catch (exception) { }
};
and the worker was like this:
;(function(self, undefined) {
var receivedStopCmd = false;
self.addEventListener('message', function(e){
if (e.data.cmd === 'eftCmdStart') {
// kick off processing here
EftSendGetRequest('...');
}
if (e.data.cmd === 'eftCmdStop') {
// xhr might be in process, this just updates what
// the onload function does.
receivedStopCmd = true;
}
}, false);
var EftSendGetRequest = function(passedUrl) {
if (xmlHttpReq === undefined) {
xmlHttpReq = new XMLHttpRequest();
}
try {
xmlHttpReq.open("GET", passedUrl, false);
xmlHttpReq.onload = function(){
if (!receivedStopCmd) {
// post response and/or keep polling
self.postMessage('whatever the message is');
} else {
// (1) stop polling so no more
// requests are sent..if this
// requires doing anyhting
// (2) Send a message that this worker can be terminated.
self.postMessage('readyToTerminate');
}
};
xmlHttpReq.send();
} catch (e) { }
return xmlHttpReq.responseText;
};
})(self);
This would allow the XHR to manage itself. I didn't run this of course.. its just an example of the approach I would take on the question.

Asynchronous Script Loading Callback

http://jsfiddle.net/JamesKyle/HQDu6/
I've created a short function based on Mathias Bynens Optimization of the Google Analytics asynchronous script that goes as following:
function async(src) {
var d = document, t = 'script',
o = d.createElement(t),
s = d.getElementsByTagName(t)[0];
o.src = '//' + src;
s.parentNode.insertBefore(o, s);
}
This works great and I've already started using it for several different scripts
// Crazy Egg
async('dnn506yrbagrg.cloudfront.net/pages/scripts/XXXXX/XXXXX.js?' + Math.floor(new Date().getTime() / 3600000));
// User Voice
var uvOptions = {};
async('widget.uservoice.com/XXXXX.js');
// Google Analytics
var _gaq = [['_setAccount', 'UA-XXXXX-XX'], ['_setDomainName', 'coachup.com'], ['_trackPageview']];
async('google-analytics.com/ga.js');
// Stripe
async('js.stripe.com/v1');​
The problem comes when I encounter a script that needs to be called after it's loaded:
// Snap Engage
async('snapabug.appspot.com/snapabug.js');
SnapABug.init('XXXXX-XXXXX-XXXXX-XXXXX-XXXXX');
So I figured I'd turn this into a callback function that would be used as so:
async('snapabug.appspot.com/snapabug.js', function() {
SnapABug.init('XXXXX-XXXXX-XXXXX-XXXXX-XXXXX');
});
I did not expect that this would be difficult for me to do but it has turned out that way.
My question is what is the most efficient way to add a callback without overcomplicating the code.
See the jsfiddle: http://jsfiddle.net/JamesKyle/HQDu6/
Thanks RASG for https://stackoverflow.com/a/3211647/982924
Async function with callback:
function async(u, c) {
var d = document, t = 'script',
o = d.createElement(t),
s = d.getElementsByTagName(t)[0];
o.src = '//' + u;
if (c) { o.addEventListener('load', function (e) { c(null, e); }, false); }
s.parentNode.insertBefore(o, s);
}
Usage:
async('snapabug.appspot.com/snapabug.js', function() {
SnapABug.init('XXXXX-XXXXX-XXXXX-XXXXX-XXXXX');
});
jsFiddle
A more recent snippet:
async function loadAsync(src) {
const script = document.createElement('script');
script.src = src;
return new Promise((resolve, reject) => {
script.onreadystatechange = function () {
if (script.readyState === 'loaded' || script.readyState === 'complete') {
script.onreadystatechange = null;
resolve(true);
}
};
document.getElementsByTagName('head')[0].appendChild(script);
});
}
utilisation
loadAsync(`https://....js`).then(_ => {
// ... script loaded here
})
James Kyle's answer doesn't take IE9 into account. Here is a modified version of the code I found in the link proposed in the comments. Modify the var baseUrl so it can find the script accordingly.
//for requiring a script loaded asynchronously.
function loadAsync(src, callback, relative){
var baseUrl = "/resources/script/";
var script = document.createElement('script');
if(relative === true){
script.src = baseUrl + src;
}else{
script.src = src;
}
if(callback !== null){
if (script.readyState) { // IE, incl. IE9
script.onreadystatechange = function() {
if (script.readyState === "loaded" || script.readyState === "complete") {
script.onreadystatechange = null;
callback();
}
};
} else {
script.onload = function() { // Other browsers
callback();
};
}
}
document.getElementsByTagName('head')[0].appendChild(script);
}
utilisation:
loadAsync('https://www.gstatic.com/charts/loader.js' , function(){
chart.loadCharts();
});
// OR relative path
loadAsync('fastclick.js', null, true);
The other answers works well, but aren't super readable or require Promises. Here is my two cents:
function loadScript(src, callback) {
var script = document.createElement('script');
script.setAttribute('src', src);
script.addEventListener('load', callback);
document.head.appendChild(script);
},

Categories