How to catch "Failed to load module script" [duplicate] - javascript
I'm dynamically adding <script> tags to a page's <head>, and I'd like to be able to tell whether the loading failed in some way -- a 404, a script error in the loaded script, whatever.
In Firefox, this works:
var script_tag = document.createElement('script');
script_tag.setAttribute('type', 'text/javascript');
script_tag.setAttribute('src', 'http://fail.org/nonexistant.js');
script_tag.onerror = function() { alert("Loading failed!"); }
document.getElementsByTagName('head')[0].appendChild(script_tag);
However, this doesn't work in IE or Safari.
Does anyone know of a way to make this work in browsers other than Firefox?
(I don't think a solution that requires placing special code within the .js files is a good one. It's inelegant and inflexible.)
UPDATE 2021:
All browsers today support onerror="" on script tags, examples:
Building script tag in js on MDN
Html example by #Rudey in comments: <script src="nonexistent.js" onerror="alert('error!')"></script>
Original comment from 2010:
If you only care about html5 browsers you can use error event.
From the spec:
If the src attribute's value is the
empty string or if it could not be
resolved, then the user agent must
queue a task to fire a simple event
named error at the element, and
abort these steps.
(...)
If the load resulted in an error (for
example a DNS error, or an HTTP 404
error) Executing the script block must
just consist of firing a simple event
named error at the element.
This means you don't have to do any error prone polling and can combine it with async and defer attribute to make sure the script is not blocking page rendering:
The defer attribute may be specified
even if the async attribute is
specified, to cause legacy Web
browsers that only support defer (and
not async) to fall back to the defer
behavior instead of the synchronous
blocking behavior that is the default.
More on http://www.w3.org/TR/html5/scripting-1.html#script
There is no error event for the script tag. You can tell when it is successful, and assume that it has not loaded after a timeout:
<script type="text/javascript" onload="loaded=1" src="....js"></script>
my working clean solution (2017)
function loaderScript(scriptUrl){
return new Promise(function (res, rej) {
let script = document.createElement('script');
script.src = scriptUrl;
script.type = 'text/javascript';
script.onerror = rej;
script.async = true;
script.onload = res;
script.addEventListener('error',rej);
script.addEventListener('load',res);
document.head.appendChild(script);
})
}
As Martin pointed, used like that:
const event = loaderScript("myscript.js")
.then(() => { console.log("loaded"); })
.catch(() => { console.log("error"); });
OR
try{
await loaderScript("myscript.js")
console.log("loaded");
}catch{
console.log("error");
}
The script from Erwinus works great, but isn't very clearly coded. I took the liberty to clean it up and decipher what it was doing. I've made these changes:
Meaningful variable names
Use of prototype.
require() uses an argument variable
No alert() messages are returned by default
Fixed some syntax errors and scope issues I was getting
Thanks again to Erwinus, the functionality itself is spot on.
function ScriptLoader() {
}
ScriptLoader.prototype = {
timer: function (times, // number of times to try
delay, // delay per try
delayMore, // extra delay per try (additional to delay)
test, // called each try, timer stops if this returns true
failure, // called on failure
result // used internally, shouldn't be passed
) {
var me = this;
if (times == -1 || times > 0) {
setTimeout(function () {
result = (test()) ? 1 : 0;
me.timer((result) ? 0 : (times > 0) ? --times : times, delay + ((delayMore) ? delayMore : 0), delayMore, test, failure, result);
}, (result || delay < 0) ? 0.1 : delay);
} else if (typeof failure == 'function') {
setTimeout(failure, 1);
}
},
addEvent: function (el, eventName, eventFunc) {
if (typeof el != 'object') {
return false;
}
if (el.addEventListener) {
el.addEventListener(eventName, eventFunc, false);
return true;
}
if (el.attachEvent) {
el.attachEvent("on" + eventName, eventFunc);
return true;
}
return false;
},
// add script to dom
require: function (url, args) {
var me = this;
args = args || {};
var scriptTag = document.createElement('script');
var headTag = document.getElementsByTagName('head')[0];
if (!headTag) {
return false;
}
setTimeout(function () {
var f = (typeof args.success == 'function') ? args.success : function () {
};
args.failure = (typeof args.failure == 'function') ? args.failure : function () {
};
var fail = function () {
if (!scriptTag.__es) {
scriptTag.__es = true;
scriptTag.id = 'failed';
args.failure(scriptTag);
}
};
scriptTag.onload = function () {
scriptTag.id = 'loaded';
f(scriptTag);
};
scriptTag.type = 'text/javascript';
scriptTag.async = (typeof args.async == 'boolean') ? args.async : false;
scriptTag.charset = 'utf-8';
me.__es = false;
me.addEvent(scriptTag, 'error', fail); // when supported
// when error event is not supported fall back to timer
me.timer(15, 1000, 0, function () {
return (scriptTag.id == 'loaded');
}, function () {
if (scriptTag.id != 'loaded') {
fail();
}
});
scriptTag.src = url;
setTimeout(function () {
try {
headTag.appendChild(scriptTag);
} catch (e) {
fail();
}
}, 1);
}, (typeof args.delay == 'number') ? args.delay : 1);
return true;
}
};
$(document).ready(function () {
var loader = new ScriptLoader();
loader.require('resources/templates.js', {
async: true, success: function () {
alert('loaded');
}, failure: function () {
alert('NOT loaded');
}
});
});
I know this is an old thread but I got a nice solution to you (I think). It's copied from an class of mine, that handles all AJAX stuff.
When the script cannot be loaded, it set an error handler but when the error handler is not supported, it falls back to a timer that checks for errors for 15 seconds.
function jsLoader()
{
var o = this;
// simple unstopable repeat timer, when t=-1 means endless, when function f() returns true it can be stopped
o.timer = function(t, i, d, f, fend, b)
{
if( t == -1 || t > 0 )
{
setTimeout(function() {
b=(f()) ? 1 : 0;
o.timer((b) ? 0 : (t>0) ? --t : t, i+((d) ? d : 0), d, f, fend,b );
}, (b || i < 0) ? 0.1 : i);
}
else if(typeof fend == 'function')
{
setTimeout(fend, 1);
}
};
o.addEvent = function(el, eventName, eventFunc)
{
if(typeof el != 'object')
{
return false;
}
if(el.addEventListener)
{
el.addEventListener (eventName, eventFunc, false);
return true;
}
if(el.attachEvent)
{
el.attachEvent("on" + eventName, eventFunc);
return true;
}
return false;
};
// add script to dom
o.require = function(s, delay, baSync, fCallback, fErr)
{
var oo = document.createElement('script'),
oHead = document.getElementsByTagName('head')[0];
if(!oHead)
{
return false;
}
setTimeout( function() {
var f = (typeof fCallback == 'function') ? fCallback : function(){};
fErr = (typeof fErr == 'function') ? fErr : function(){
alert('require: Cannot load resource -'+s);
},
fe = function(){
if(!oo.__es)
{
oo.__es = true;
oo.id = 'failed';
fErr(oo);
}
};
oo.onload = function() {
oo.id = 'loaded';
f(oo);
};
oo.type = 'text/javascript';
oo.async = (typeof baSync == 'boolean') ? baSync : false;
oo.charset = 'utf-8';
o.__es = false;
o.addEvent( oo, 'error', fe ); // when supported
// when error event is not supported fall back to timer
o.timer(15, 1000, 0, function() {
return (oo.id == 'loaded');
}, function(){
if(oo.id != 'loaded'){
fe();
}
});
oo.src = s;
setTimeout(function() {
try{
oHead.appendChild(oo);
}catch(e){
fe();
}
},1);
}, (typeof delay == 'number') ? delay : 1);
return true;
};
}
$(document).ready( function()
{
var ol = new jsLoader();
ol.require('myscript.js', 800, true, function(){
alert('loaded');
}, function() {
alert('NOT loaded');
});
});
To check if the javascript in nonexistant.js returned no error you have to add a variable inside http://fail.org/nonexistant.js like var isExecuted = true; and then check if it exists when the script tag is loaded.
However if you only want to check that the nonexistant.js returned without a 404 (meaning it exists), you can try with a isLoaded variable ...
var isExecuted = false;
var isLoaded = false;
script_tag.onload = script_tag.onreadystatechange = function() {
if(!this.readyState ||
this.readyState == "loaded" || this.readyState == "complete") {
// script successfully loaded
isLoaded = true;
if(isExecuted) // no error
}
}
This will cover both cases.
I hope this doesn't get downvoted, because in special circumstances it is the most reliable way to solve the problem. Any time the server allows you to get a Javascript resource using CORS (http://en.wikipedia.org/wiki/Cross-origin_resource_sharing), you have a rich array of options to do so.
Using XMLHttpRequest to fetch resources will work across all modern browsers, including IE. Since you are looking to load Javascript, you have Javascript available to you in the first place. You can track the progress using the readyState (http://en.wikipedia.org/wiki/XMLHttpRequest#The_onreadystatechange_event_listener). Finally, once you receive the content of the file, you can execute it with eval ( ). Yes, I said eval -- because security-wise it is no different from loading the script normally. In fact, a similar technique is suggested by John Resig to have nicer tags (http://ejohn.org/blog/degrading-script-tags/).
This method also lets you separate the loading from the eval, and execute functions before and after the eval happens. It becomes very useful when loading scripts in parallel but evaluating them one after the other -- something browsers can do easily when you place the tags in HTML, but don't let you by adding scripts at run-time with Javascript.
CORS is also preferable to JSONP for loading scripts (http://en.wikipedia.org/wiki/XMLHttpRequest#Cross-domain_requests). However, if you are developing your own third-party widgets to be embedded in other sites, you should actually load the Javascript files from your own domain in your own iframe (again, using AJAX)
In short:
Try to see if you can load the resource using AJAX GET
Use eval after it has successfully loaded
To improve it:
Check out the cache-control headers being sent
Look into otherwise caching the content in localStorage, if you need to
Check out Resig's "degrading javascript" for cleaner code
Check out require.js
This trick worked for me, although I admit that this is probably not the best way to solve this problem. Instead of trying this, you should see why the javascripts aren't loading. Try keeping a local copy of the script in your server, etc. or check with the third party vendor from where you are trying to download the script.
Anyways, so here's the workaround:
1) Initialize a variable to false
2) Set it to true when the javascript loads (using the onload attribute)
3) check if the variable is true or false once the HTML body has loaded
<html>
<head>
<script>
var scriptLoaded = false;
function checkScriptLoaded() {
if (scriptLoaded) {
// do something here
} else {
// do something else here!
}
}
</script>
<script src="http://some-external-script.js" onload="scriptLoaded=true;" />
</head>
<body onload="checkScriptLoaded()">
<p>My Test Page!</p>
</body>
</html>
Here is another JQuery-based solution without any timers:
<script type="text/javascript">
function loadScript(url, onsuccess, onerror) {
$.get(url)
.done(function() {
// File/url exists
console.log("JS Loader: file exists, executing $.getScript "+url)
$.getScript(url, function() {
if (onsuccess) {
console.log("JS Loader: Ok, loaded. Calling onsuccess() for " + url);
onsuccess();
console.log("JS Loader: done with onsuccess() for " + url);
} else {
console.log("JS Loader: Ok, loaded, no onsuccess() callback " + url)
}
});
}).fail(function() {
// File/url does not exist
if (onerror) {
console.error("JS Loader: probably 404 not found. Not calling $.getScript. Calling onerror() for " + url);
onerror();
console.error("JS Loader: done with onerror() for " + url);
} else {
console.error("JS Loader: probably 404 not found. Not calling $.getScript. No onerror() callback " + url);
}
});
}
</script>
Thanks to:
https://stackoverflow.com/a/14691735/1243926
Sample usage (original sample from JQuery getScript documentation):
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>jQuery.getScript demo</title>
<style>
.block {
background-color: blue;
width: 150px;
height: 70px;
margin: 10px;
}
</style>
<script src="http://code.jquery.com/jquery-1.9.1.js"></script>
</head>
<body>
<button id="go">» Run</button>
<div class="block"></div>
<script>
function loadScript(url, onsuccess, onerror) {
$.get(url)
.done(function() {
// File/url exists
console.log("JS Loader: file exists, executing $.getScript "+url)
$.getScript(url, function() {
if (onsuccess) {
console.log("JS Loader: Ok, loaded. Calling onsuccess() for " + url);
onsuccess();
console.log("JS Loader: done with onsuccess() for " + url);
} else {
console.log("JS Loader: Ok, loaded, no onsuccess() callback " + url)
}
});
}).fail(function() {
// File/url does not exist
if (onerror) {
console.error("JS Loader: probably 404 not found. Not calling $.getScript. Calling onerror() for " + url);
onerror();
console.error("JS Loader: done with onerror() for " + url);
} else {
console.error("JS Loader: probably 404 not found. Not calling $.getScript. No onerror() callback " + url);
}
});
}
loadScript("https://raw.github.com/jquery/jquery-color/master/jquery.color.js", function() {
console.log("loaded jquery-color");
$( "#go" ).click(function() {
$( ".block" )
.animate({
backgroundColor: "rgb(255, 180, 180)"
}, 1000 )
.delay( 500 )
.animate({
backgroundColor: "olive"
}, 1000 )
.delay( 500 )
.animate({
backgroundColor: "#00f"
}, 1000 );
});
}, function() { console.error("Cannot load jquery-color"); });
</script>
</body>
</html>
This can be done safely using promises
function loadScript(src) {
return new Promise(function(resolve, reject) {
let script = document.createElement('script');
script.src = src;
script.onload = () => resolve(script);
script.onerror = () => reject(new Error("Script load error: " + src));
document.head.append(script);
});
}
and use like this
let promise = loadScript("https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.2.0/lodash.js");
promise.then(
script => alert(`${script.src} is loaded!`),
error => alert(`Error: ${error.message}`)
);
onerror Event
*Update August 2017: onerror is fired by Chrome and Firefox. onload is fired by Internet Explorer. Edge fires neither onerror nor onload. I wouldnt use this method but it could work in some cases. See also
<link> onerror do not work in IE
*
Definition and Usage
The onerror event is triggered if an error occurs while loading an external file (e.g. a document or an image).
Tip: When used on audio/video media, related events that occurs when there is some kind of disturbance to the media loading process, are:
onabort
onemptied
onstalled
onsuspend
In HTML:
element onerror="myScript">
In JavaScript, using the addEventListener() method:
object.addEventListener("error", myScript);
Note: The addEventListener() method is not supported in Internet Explorer 8 and earlier versions.
Example
Execute a JavaScript if an error occurs when loading an image:
img src="image.gif" onerror="myFunction()">
The reason it doesn't work in Safari is because you're using attribute syntax. This will work fine though:
script_tag.addEventListener('error', function(){/*...*/}, true);
...except in IE.
If you want to check the script executed successfully, just set a variable using that script and check for it being set in the outer code.
This doesn't need jquery, doesn't need to load the script async, needs no timer nor to have the loaded script set a value. I've tested it in FF, Chrome, and Safari.
<script>
function loadScript(src) {
return new Promise(function(resolve, reject) {
let s = window.document.createElement("SCRIPT");
s.onload = () => resolve(s);
s.onerror = () => reject(new Error(src));
s.src = src;
// don't bounce to global handler on 404.
s.addEventListener('error', function() {});
window.document.head.append(s);
});
}
let successCallback = (result) => {
console.log(scriptUrl + " loaded.");
}
let failureCallback = (error) => {
console.log("load failed: " + error.message);
}
loadScript(scriptUrl).then(successCallback, failureCallback);
</script>
It was proposed to set a timeout and then assume load failure after a timeout.
setTimeout(fireCustomOnerror, 4000);
The problem with that approach is that the assumption is based on chance. After your timeout expires, the request is still pending. The request for the pending script may load, even after the programmer assumed that load won't happen.
If the request could be canceled, then the program could wait for a period, then cancel the request.
This is how I used a promise to detect loading errors that are emited on the window object:
<script type='module'>
window.addEventListener('error', function(error) {
let url = error.filename
url = url.substring(0, (url.indexOf("#") == -1) ? url.length : url.indexOf("#"));
url = url.substring(0, (url.indexOf("?") == -1) ? url.length : url.indexOf("?"));
url = url.substring(url.lastIndexOf("/") + 1, url.length);
window.scriptLoadReject && window.scriptLoadReject[url] && window.scriptLoadReject[url](error);
}, true);
window.boot=function boot() {
const t=document.createElement('script');
t.id='index.mjs';
t.type='module';
new Promise((resolve, reject) => {
window.scriptLoadReject = window.scriptLoadReject || {};
window.scriptLoadReject[t.id] = reject;
t.addEventListener('error', reject);
t.addEventListener('load', resolve); // Careful load is sometimes called even if errors prevent your script from running! This promise is only meant to catch errors while loading the file.
}).catch((value) => {
document.body.innerHTML='Error loading ' + t.id + '! Please reload this webpage.<br/>If this error persists, please try again later.<div><br/>' + t.id + ':' + value.lineno + ':' + value.colno + '<br/>' + (value && value.message);
});
t.src='./index.mjs'+'?'+new Date().getTime();
document.head.appendChild(t);
};
</script>
<script nomodule>document.body.innerHTML='This website needs ES6 Modules!<br/>Please enable ES6 Modules and then reload this webpage.';</script>
</head>
<body onload="boot()" style="margin: 0;border: 0;padding: 0;text-align: center;">
<noscript>This website needs JavaScript!<br/>Please enable JavaScript and then reload this webpage.</noscript>
Well, the only way I can think of doing everything you want is pretty ugly. First perform an AJAX call to retrieve the Javascript file contents. When this completes you can check the status code to decide if this was successful or not. Then take the responseText from the xhr object and wrap it in a try/catch, dynamically create a script tag, and for IE you can set the text property of the script tag to the JS text, in all other browsers you should be able to append a text node with the contents to script tag. If there's any code that expects a script tag to actually contain the src location of the file, this won't work, but it should be fine for most situations.
Related
Enqueue external JS by inline JS and initialize a variable from the external JS [duplicate]
In my page body, I need to insert this code as the result of an AJAX call: <p>Loading jQuery</p> <script type='text/javascript' src='scripts/jquery/core/jquery-1.4.4.js'></script> <p>Using jQuery</p> <script type='text/javascript'> $.ajax({ ... }); </script> I can't use $.load() since the document has already loaded, so the event doesn't fire. Is this safe? If not, how do I make sure the jquery script has loaded before my custom, generated code is executed.
Add an ID to your script file so you can query it. <script id="hljs" async src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.0.0/highlight.min.js"></script> Then add a load listener to it in JavaScript <script> var script = document.querySelector('#hljs'); script.addEventListener('load', function() { hljs.initHighlightingOnLoad(); }); </script>
It is pretty safe. Historically, <script> tags are full blocking, hence the second <script> tag can't get encountered befored the former has finished parsing/excuting. Only problem might be that "modern" browsers tend to load scripts asynchronously and deferred. So to make sure order is correct, use it like this: <p>Loading jQuery</p> <script type='text/javascript' async=false defer=false src='scripts/jquery/core/jquery-1.4.4.js'></script> <p>Using jQuery</p> <script type='text/javascript'> $.ajax({ ... }); </script> However, it's probably a better idea it use dynamic script tag insertion instead of pushing this as HTML string into the DOM. Would be the same story var scr = document.createElement('script'), head = document.head || document.getElementsByTagName('head')[0]; scr.src = 'scripts/jquery/core/jquery-1.4.4.js'; scr.async = false; // optionally head.insertBefore(scr, head.firstChild);
const jsScript = document.createElement('script') jsScript.src = 'https://coolJavascript.js' document.body.appendChild(jsScript) jsScript.addEventListener('load', () => { doSomethingNow() }) Will load after the script is dynamically added
Wait for multiple scripts to load The following helper loads multiple scripts only once and returns a promise: async function cirosantilli_load_scripts(script_urls) { function load(script_url) { return new Promise(function(resolve, reject) { if (cirosantilli_load_scripts.loaded.has(script_url)) { resolve(); } else { var script = document.createElement('script'); script.onload = resolve; script.src = script_url document.head.appendChild(script); } }); } var promises = []; for (const script_url of script_urls) { promises.push(load(script_url)); } await Promise.all(promises); for (const script_url of script_urls) { cirosantilli_load_scripts.loaded.add(script_url); } } cirosantilli_load_scripts.loaded = new Set(); (async () => { await cirosantilli_load_scripts([ 'https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/1.3.8/FileSaver.min.js', 'https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.min.js', ]); // Now do stuff with those scripts. })(); GitHub upstream: definition and usage. Tested in Chromium 75.
There is also new feature in jQuery 1.6. It is called jQuery.holdReady(). It is actually self explanatory; when you call jQuery.holdReady(true), ready event is not fired until you call jQuery.holdReady(false). Setting this to false will not automatically fire a ready event, it just removes the hold. Here is a non-blocking example of loading a script taken from the documentation: $.holdReady(true); $.getScript("myplugin.js", function() { $.holdReady(false); }); See http://api.jquery.com/jQuery.holdReady/ for more information
this works for me function loadScript(sources, callBack) { var script = document.createElement('script'); script.src = sources; script.async = false; document.body.appendChild(script); script.addEventListener('load', () => { if(typeof callBack == "function") callBack(sources); }); }
In my case the solutions didn't work. I wanted to programmatically click a link after a script has loaded the right click event for the link. Thus I had to work with timeout and loop: <script> var ifrbutton = document.getElementById("id-of-link"); if(typeof(ifrbutton) != 'undefined' && ifrbutton != null && window.location.hash == "#special-hash") { var i = 0; // store the interval id to clear in future var intr = setInterval(function() { if (ifrbutton.onclick!=null) { ifrbutton.click(); clearInterval(intr); i = 200; } if (++i >= 200) clearInterval(intr); }, 300) } </script> Thus the solution could also be to check in intervals for certain functionality that the script brings with it... Set some secure end just in case the script gets never loaded... Works safely for me ;)
new MutationObserver((mutationsList, observer) => { for (var mutation of mutationsList) if (mutation.type === 'childList') Array.from (mutation.addedNodes) .filter (node => node.tagName === 'SCRIPT') .forEach (script => { //Script started loading script.addEventListener ('load', () => { //Script finished loading }) }) }).observe(document, { attributes: false, childList: true, subtree: true });
The answer from Ciro Santilli is excellent. I have improved it a little bit because I faced some issues. I made a special form field based on CodeMirror editor. Since the script for the CodeMirror is huge, I load it only on demand by field initialization. If the form has 2 such fields, the loading of the script is started twice merely at the same moment, since the first started script is not loaded yet, it is actually loaded twice, what is bad. The functions MyNamespace.loadScript(script_url) and MyNamespace.loadScripts(script_urls) always return a Promise so that you can decide whether to use await or then. If the script is already loaded, it returns a Promise that resolves immediately. If the script is being loaded, it returns a Promise with interval observing that resolves if the script is loaded. If the script was never loaded, it returns a Promise with loading script that resolves by loaded event. The code: MyNamespace.loadScript = function (script_url) { console.log("loading: " + script_url); if (!MyNamespace.loadedScripts) MyNamespace.loadedScripts = new Set(); if (!MyNamespace.loadingScripts) MyNamespace.loadingScripts = new Set(); if (MyNamespace.loadedScripts.has(script_url)) { return new Promise(function (resolve, reject) { console.log("already loaded"); resolve(); }); } if (MyNamespace.loadingScripts.has(script_url)) { return new Promise(function (resolve, reject) { console.log("loading in progress"); let interval = setInterval(function () { if (MyNamespace.loadedScripts.has(script_url)) { clearInterval(interval); console.log("waited until loaded"); resolve(); } }, 200); }); } MyNamespace.loadingScripts.add(script_url); return new Promise(function (resolve, reject) { let script = document.createElement('script'); script.src = script_url; script.onload = function () { console.log("actually loaded"); MyNamespace.loadedScripts.add(script_url); resolve(); }; script.onerror = function () { console.log("load error"); reject(new Error("Error by loading the script " + script_url)); }; document.head.append(script); }); }; MyNamespace.loadScripts = function (script_urls) { let promises = []; for (let script_url of script_urls) { promises.push(MyNamespace.loadScript(script_url)); } return Promise.all(promises); };
$.ajax and $.load are the same thing. You can use either. If you put $.load in a script tag on the page it will fire when it loads just like $.ajax(). When it comes to waiting for a script to fire before you do something what Tomalak said is kinda true. The exception is asynchronous calls. Javascript will make an AJAX call then continue running the script and when the AJAX call responds it will run the success/fail depending on what you tell it to do. Now say you want your JS to wait for the return, with a single call it's pretty easy just wrap every thing in the success callback, or you could do it the cool way and use $.deferred. This allows you to make a bunch of ajax calls or one and then run some code only after the finish. $.when & $.then are probably the best for this situation. Any way what your are doing is safe.
anonymous window.setTimeout function reloading page
Using Tampermonkey to change the behavior of a website. Have some problems with a website with the following code: <script language="JavaScript"> if (typeof jQuery != 'undefined') { jQuery(window).load(function() { window.setTimeout(function() { window.location.replace(window.location.href); }, 180E3); }); } </script> How does one remove/prevent it reloading the page?
Without messing that much with jQuery, if your script runs before that piece of code you can do the following: jQuery.fn.load = function() { console.log("Tried to reload the page!") } // Then the following, outputs 'Tried to reload the page!' and does nothing else jQuery(window).load(function() { // code }) If you still need the load function afterwards you could do the following: var oldLoad = jQuery.fn.load var undesiredCallback = "function() { window.setTimeout(function() { window.location.replace(window.location.href); }, 180E3); }" jQuery.fn.load = function(callback) { if(callback.toString() !== undesiredCallback) { oldLoad(callback); } } But it's browser dependent and very unreliable Another way would be adding a onbeforeunload event, but that would pop a prompt on your tab: window.onbeforeunload = function() { return "Stay here please"; }
You can also override setTimeout functionallity. var oldSetTimeout = window.setTimeout; window.setTimeout = function(func,interval){ if(typeof func === "function" && func.toString().indexOf('window.location.replace(window.location.href)') > -1){ /*do nothing*/ }else{ oldSetTimeout(func,interval) } }
Adding disqus to a meteor.js project
i am trying to add the disqus commentsystem to my application. I followed the instruction written in this article KLICK I created a template called disqus.html <template name="disqus"> {{#isolate}} <div id="disqus_thread"></div> <noscript>Please enable JavaScript to view the comments powered by Disqus.</noscript> comments powered by <span class="logo-disqus">Disqus</span> {{/isolate}} </template> If this template is rendered, embed.js should load once and disqus makes a reset. Template.disqus.rendered = function() { Session.set("loadDisqus", true); return typeof DISQUS !== "undefined" && DISQUS !== null ? DISQUS.reset({ reload: true, config: function() {} }) : void 0; }; React on sessionchange in deps.autorun Meteor.startup (function () { Deps.autorun(function() { if(Session.get("loadDisqus") && !window.DISQUS) { var disqus_shortname = '<example>'; // required: replace example with your forum shortname (function() { var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); })(); } }); }); This works fine in Firefox 25.0.1. I can login, logout and create comments. But it isnt working in Chrome 31.0.1650.57 m. It is not possible for me to login. No error is thrown. What can i do? Any suggestions? Even a login to disqus in discovermeteor.com/... is not possible for me.
Using session here is interesting but unnecessary. Here is what i migth do in your disqus.js file: var isDisqusLoaded = false, myScriptLoader = function funcMyScriptLoader(jsEl, callback) { if (window.attachEvent) { // for IE (sometimes it doesn't send loaded event but only complete) jsEl.onreadystatechange = function funcOnReadyStateChange() { if (jsEl.readyState === 'complete') { jsEl.onreadystatechange = ""; } else if (jsEl.readyState === 'loaded') { jsEl.onreadystatechange = ""; } if (typeof callback === 'function') { callback(); } }; } else { // most browsers jsEl.onload = function funcOnLoad () { if (typeof callback === 'function') { callback(); } }; } }; Template.disqus.rendered = function funcTplDisqusRendered() { if (!isDisqusLoaded) { var myElJs = document.createElement('script'), s = document.getElementsByTagName('script')[0]; myElJs.type = 'text/javascript'; myElJs.async = true; myElJs.src = '//' + disqus_shortname + '.disqus.com/embed.js'; myScriptLoader(myElJs, function funcEventLoaded() { isDisqusLoaded = true; }); s.parentNode.insertBefore(myElJs, s); } }; Using that script you won't need to use Deps.autorun and Session. You should use that kind of feature only where you want to get realtime, but if don't need it, avoid it coz it will degrade your app performance. You can add Disqus.reset if it's really needed but i'm not sure, look at the disqus documentation. I didn't test the script but it should be ok.
Sounds like a weird cacheing error, have you tried resetting chrome's cache?
Bookmarklet wait until Javascript is loaded
I've got a bookmarklet which loads jQuery and some other js libraries. How do I: Wait until the javascript library I'm using is available/loaded. If I try to use the script before it has finished loading, like using the $ function with jQuery before it's loaded, an undefined exception is thrown. Insure that the bookmarklet I load won't be cached (without using a server header, or obviously, being that this is a javascript file: a metatag) Is anyone aware if onload for dynamically added javascript works in IE? (to contradict this post) What's the simplest solution, cleanest resolution to these issues?
It depends on how you are actually loading jQuery. If you are appending a script element to the page, you can use the same technique that jQuery uses to dynamically load a script. EDIT: I did my homework and actually extracted a loadScript function from the jQuery code to use in your bookmarklet. It might actually be useful to many (including me). function loadScript(url, callback) { var head = document.getElementsByTagName("head")[0]; var script = document.createElement("script"); script.src = url; // Attach handlers for all browsers var done = false; script.onload = script.onreadystatechange = function() { if( !done && ( !this.readyState || this.readyState == "loaded" || this.readyState == "complete") ) { done = true; // Continue your code callback(); // Handle memory leak in IE script.onload = script.onreadystatechange = null; head.removeChild( script ); } }; head.appendChild(script); } // Usage: // This code loads jQuery and executes some code when jQuery is loaded loadScript("https://code.jquery.com/jquery-latest.js", function() { $('my_element').hide(); });
To answer your first question: Javascript is interpreted sequentially, so any following bookmarklet code will not execute until the library is loaded (assuming the library was interpreted successfully - no syntax errors). To prevent the files from being cached, you can append a meaningless query string... url = 'jquery.js?x=' + new Date().getTime();
I've paid an attention that in Chrome the order of scripts that are loaded is undetermined, when using #Vincent Robert's technique. In this case a little modification helps: (function() { var callback = function() { // Do you work }; // check for our library existence if (typeof (MyLib) == 'undefined') { var sources = [ 'http://ajax.cdnjs.com/ajax/libs/json2/20110223/json2.js', 'https://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js', 'https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.13/jquery-ui.min.js', 'http://myhost.com/javascripts/mylib.min.js']; var loadNextScript = function() { if (sources.length > 0) { var script = document.createElement('script'); script.src = sources.shift(); document.body.appendChild(script); var done = false; script.onload = script.onreadystatechange = function() { if (!done && (!this.readyState || this.readyState == "loaded" || this.readyState == "complete")) { done = true; // Handle memory leak in IE script.onload = script.onreadystatechange = null; loadNextScript(); } } } else { callback(); } } loadNextScript(); } else { callback(); } })();
I got a little closer with this, but not completely. It would be nice to have a discrete, example of a bookmarklet that demonstrated how to avoided caching.
How to tell if a <script> tag failed to load
I'm dynamically adding <script> tags to a page's <head>, and I'd like to be able to tell whether the loading failed in some way -- a 404, a script error in the loaded script, whatever. In Firefox, this works: var script_tag = document.createElement('script'); script_tag.setAttribute('type', 'text/javascript'); script_tag.setAttribute('src', 'http://fail.org/nonexistant.js'); script_tag.onerror = function() { alert("Loading failed!"); } document.getElementsByTagName('head')[0].appendChild(script_tag); However, this doesn't work in IE or Safari. Does anyone know of a way to make this work in browsers other than Firefox? (I don't think a solution that requires placing special code within the .js files is a good one. It's inelegant and inflexible.)
UPDATE 2021: All browsers today support onerror="" on script tags, examples: Building script tag in js on MDN Html example by #Rudey in comments: <script src="nonexistent.js" onerror="alert('error!')"></script> Original comment from 2010: If you only care about html5 browsers you can use error event. From the spec: If the src attribute's value is the empty string or if it could not be resolved, then the user agent must queue a task to fire a simple event named error at the element, and abort these steps. (...) If the load resulted in an error (for example a DNS error, or an HTTP 404 error) Executing the script block must just consist of firing a simple event named error at the element. This means you don't have to do any error prone polling and can combine it with async and defer attribute to make sure the script is not blocking page rendering: The defer attribute may be specified even if the async attribute is specified, to cause legacy Web browsers that only support defer (and not async) to fall back to the defer behavior instead of the synchronous blocking behavior that is the default. More on http://www.w3.org/TR/html5/scripting-1.html#script
There is no error event for the script tag. You can tell when it is successful, and assume that it has not loaded after a timeout: <script type="text/javascript" onload="loaded=1" src="....js"></script>
my working clean solution (2017) function loaderScript(scriptUrl){ return new Promise(function (res, rej) { let script = document.createElement('script'); script.src = scriptUrl; script.type = 'text/javascript'; script.onerror = rej; script.async = true; script.onload = res; script.addEventListener('error',rej); script.addEventListener('load',res); document.head.appendChild(script); }) } As Martin pointed, used like that: const event = loaderScript("myscript.js") .then(() => { console.log("loaded"); }) .catch(() => { console.log("error"); }); OR try{ await loaderScript("myscript.js") console.log("loaded"); }catch{ console.log("error"); }
The script from Erwinus works great, but isn't very clearly coded. I took the liberty to clean it up and decipher what it was doing. I've made these changes: Meaningful variable names Use of prototype. require() uses an argument variable No alert() messages are returned by default Fixed some syntax errors and scope issues I was getting Thanks again to Erwinus, the functionality itself is spot on. function ScriptLoader() { } ScriptLoader.prototype = { timer: function (times, // number of times to try delay, // delay per try delayMore, // extra delay per try (additional to delay) test, // called each try, timer stops if this returns true failure, // called on failure result // used internally, shouldn't be passed ) { var me = this; if (times == -1 || times > 0) { setTimeout(function () { result = (test()) ? 1 : 0; me.timer((result) ? 0 : (times > 0) ? --times : times, delay + ((delayMore) ? delayMore : 0), delayMore, test, failure, result); }, (result || delay < 0) ? 0.1 : delay); } else if (typeof failure == 'function') { setTimeout(failure, 1); } }, addEvent: function (el, eventName, eventFunc) { if (typeof el != 'object') { return false; } if (el.addEventListener) { el.addEventListener(eventName, eventFunc, false); return true; } if (el.attachEvent) { el.attachEvent("on" + eventName, eventFunc); return true; } return false; }, // add script to dom require: function (url, args) { var me = this; args = args || {}; var scriptTag = document.createElement('script'); var headTag = document.getElementsByTagName('head')[0]; if (!headTag) { return false; } setTimeout(function () { var f = (typeof args.success == 'function') ? args.success : function () { }; args.failure = (typeof args.failure == 'function') ? args.failure : function () { }; var fail = function () { if (!scriptTag.__es) { scriptTag.__es = true; scriptTag.id = 'failed'; args.failure(scriptTag); } }; scriptTag.onload = function () { scriptTag.id = 'loaded'; f(scriptTag); }; scriptTag.type = 'text/javascript'; scriptTag.async = (typeof args.async == 'boolean') ? args.async : false; scriptTag.charset = 'utf-8'; me.__es = false; me.addEvent(scriptTag, 'error', fail); // when supported // when error event is not supported fall back to timer me.timer(15, 1000, 0, function () { return (scriptTag.id == 'loaded'); }, function () { if (scriptTag.id != 'loaded') { fail(); } }); scriptTag.src = url; setTimeout(function () { try { headTag.appendChild(scriptTag); } catch (e) { fail(); } }, 1); }, (typeof args.delay == 'number') ? args.delay : 1); return true; } }; $(document).ready(function () { var loader = new ScriptLoader(); loader.require('resources/templates.js', { async: true, success: function () { alert('loaded'); }, failure: function () { alert('NOT loaded'); } }); });
I know this is an old thread but I got a nice solution to you (I think). It's copied from an class of mine, that handles all AJAX stuff. When the script cannot be loaded, it set an error handler but when the error handler is not supported, it falls back to a timer that checks for errors for 15 seconds. function jsLoader() { var o = this; // simple unstopable repeat timer, when t=-1 means endless, when function f() returns true it can be stopped o.timer = function(t, i, d, f, fend, b) { if( t == -1 || t > 0 ) { setTimeout(function() { b=(f()) ? 1 : 0; o.timer((b) ? 0 : (t>0) ? --t : t, i+((d) ? d : 0), d, f, fend,b ); }, (b || i < 0) ? 0.1 : i); } else if(typeof fend == 'function') { setTimeout(fend, 1); } }; o.addEvent = function(el, eventName, eventFunc) { if(typeof el != 'object') { return false; } if(el.addEventListener) { el.addEventListener (eventName, eventFunc, false); return true; } if(el.attachEvent) { el.attachEvent("on" + eventName, eventFunc); return true; } return false; }; // add script to dom o.require = function(s, delay, baSync, fCallback, fErr) { var oo = document.createElement('script'), oHead = document.getElementsByTagName('head')[0]; if(!oHead) { return false; } setTimeout( function() { var f = (typeof fCallback == 'function') ? fCallback : function(){}; fErr = (typeof fErr == 'function') ? fErr : function(){ alert('require: Cannot load resource -'+s); }, fe = function(){ if(!oo.__es) { oo.__es = true; oo.id = 'failed'; fErr(oo); } }; oo.onload = function() { oo.id = 'loaded'; f(oo); }; oo.type = 'text/javascript'; oo.async = (typeof baSync == 'boolean') ? baSync : false; oo.charset = 'utf-8'; o.__es = false; o.addEvent( oo, 'error', fe ); // when supported // when error event is not supported fall back to timer o.timer(15, 1000, 0, function() { return (oo.id == 'loaded'); }, function(){ if(oo.id != 'loaded'){ fe(); } }); oo.src = s; setTimeout(function() { try{ oHead.appendChild(oo); }catch(e){ fe(); } },1); }, (typeof delay == 'number') ? delay : 1); return true; }; } $(document).ready( function() { var ol = new jsLoader(); ol.require('myscript.js', 800, true, function(){ alert('loaded'); }, function() { alert('NOT loaded'); }); });
To check if the javascript in nonexistant.js returned no error you have to add a variable inside http://fail.org/nonexistant.js like var isExecuted = true; and then check if it exists when the script tag is loaded. However if you only want to check that the nonexistant.js returned without a 404 (meaning it exists), you can try with a isLoaded variable ... var isExecuted = false; var isLoaded = false; script_tag.onload = script_tag.onreadystatechange = function() { if(!this.readyState || this.readyState == "loaded" || this.readyState == "complete") { // script successfully loaded isLoaded = true; if(isExecuted) // no error } } This will cover both cases.
I hope this doesn't get downvoted, because in special circumstances it is the most reliable way to solve the problem. Any time the server allows you to get a Javascript resource using CORS (http://en.wikipedia.org/wiki/Cross-origin_resource_sharing), you have a rich array of options to do so. Using XMLHttpRequest to fetch resources will work across all modern browsers, including IE. Since you are looking to load Javascript, you have Javascript available to you in the first place. You can track the progress using the readyState (http://en.wikipedia.org/wiki/XMLHttpRequest#The_onreadystatechange_event_listener). Finally, once you receive the content of the file, you can execute it with eval ( ). Yes, I said eval -- because security-wise it is no different from loading the script normally. In fact, a similar technique is suggested by John Resig to have nicer tags (http://ejohn.org/blog/degrading-script-tags/). This method also lets you separate the loading from the eval, and execute functions before and after the eval happens. It becomes very useful when loading scripts in parallel but evaluating them one after the other -- something browsers can do easily when you place the tags in HTML, but don't let you by adding scripts at run-time with Javascript. CORS is also preferable to JSONP for loading scripts (http://en.wikipedia.org/wiki/XMLHttpRequest#Cross-domain_requests). However, if you are developing your own third-party widgets to be embedded in other sites, you should actually load the Javascript files from your own domain in your own iframe (again, using AJAX) In short: Try to see if you can load the resource using AJAX GET Use eval after it has successfully loaded To improve it: Check out the cache-control headers being sent Look into otherwise caching the content in localStorage, if you need to Check out Resig's "degrading javascript" for cleaner code Check out require.js
This trick worked for me, although I admit that this is probably not the best way to solve this problem. Instead of trying this, you should see why the javascripts aren't loading. Try keeping a local copy of the script in your server, etc. or check with the third party vendor from where you are trying to download the script. Anyways, so here's the workaround: 1) Initialize a variable to false 2) Set it to true when the javascript loads (using the onload attribute) 3) check if the variable is true or false once the HTML body has loaded <html> <head> <script> var scriptLoaded = false; function checkScriptLoaded() { if (scriptLoaded) { // do something here } else { // do something else here! } } </script> <script src="http://some-external-script.js" onload="scriptLoaded=true;" /> </head> <body onload="checkScriptLoaded()"> <p>My Test Page!</p> </body> </html>
Here is another JQuery-based solution without any timers: <script type="text/javascript"> function loadScript(url, onsuccess, onerror) { $.get(url) .done(function() { // File/url exists console.log("JS Loader: file exists, executing $.getScript "+url) $.getScript(url, function() { if (onsuccess) { console.log("JS Loader: Ok, loaded. Calling onsuccess() for " + url); onsuccess(); console.log("JS Loader: done with onsuccess() for " + url); } else { console.log("JS Loader: Ok, loaded, no onsuccess() callback " + url) } }); }).fail(function() { // File/url does not exist if (onerror) { console.error("JS Loader: probably 404 not found. Not calling $.getScript. Calling onerror() for " + url); onerror(); console.error("JS Loader: done with onerror() for " + url); } else { console.error("JS Loader: probably 404 not found. Not calling $.getScript. No onerror() callback " + url); } }); } </script> Thanks to: https://stackoverflow.com/a/14691735/1243926 Sample usage (original sample from JQuery getScript documentation): <!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>jQuery.getScript demo</title> <style> .block { background-color: blue; width: 150px; height: 70px; margin: 10px; } </style> <script src="http://code.jquery.com/jquery-1.9.1.js"></script> </head> <body> <button id="go">» Run</button> <div class="block"></div> <script> function loadScript(url, onsuccess, onerror) { $.get(url) .done(function() { // File/url exists console.log("JS Loader: file exists, executing $.getScript "+url) $.getScript(url, function() { if (onsuccess) { console.log("JS Loader: Ok, loaded. Calling onsuccess() for " + url); onsuccess(); console.log("JS Loader: done with onsuccess() for " + url); } else { console.log("JS Loader: Ok, loaded, no onsuccess() callback " + url) } }); }).fail(function() { // File/url does not exist if (onerror) { console.error("JS Loader: probably 404 not found. Not calling $.getScript. Calling onerror() for " + url); onerror(); console.error("JS Loader: done with onerror() for " + url); } else { console.error("JS Loader: probably 404 not found. Not calling $.getScript. No onerror() callback " + url); } }); } loadScript("https://raw.github.com/jquery/jquery-color/master/jquery.color.js", function() { console.log("loaded jquery-color"); $( "#go" ).click(function() { $( ".block" ) .animate({ backgroundColor: "rgb(255, 180, 180)" }, 1000 ) .delay( 500 ) .animate({ backgroundColor: "olive" }, 1000 ) .delay( 500 ) .animate({ backgroundColor: "#00f" }, 1000 ); }); }, function() { console.error("Cannot load jquery-color"); }); </script> </body> </html>
This can be done safely using promises function loadScript(src) { return new Promise(function(resolve, reject) { let script = document.createElement('script'); script.src = src; script.onload = () => resolve(script); script.onerror = () => reject(new Error("Script load error: " + src)); document.head.append(script); }); } and use like this let promise = loadScript("https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.2.0/lodash.js"); promise.then( script => alert(`${script.src} is loaded!`), error => alert(`Error: ${error.message}`) );
onerror Event *Update August 2017: onerror is fired by Chrome and Firefox. onload is fired by Internet Explorer. Edge fires neither onerror nor onload. I wouldnt use this method but it could work in some cases. See also <link> onerror do not work in IE * Definition and Usage The onerror event is triggered if an error occurs while loading an external file (e.g. a document or an image). Tip: When used on audio/video media, related events that occurs when there is some kind of disturbance to the media loading process, are: onabort onemptied onstalled onsuspend In HTML: element onerror="myScript"> In JavaScript, using the addEventListener() method: object.addEventListener("error", myScript); Note: The addEventListener() method is not supported in Internet Explorer 8 and earlier versions. Example Execute a JavaScript if an error occurs when loading an image: img src="image.gif" onerror="myFunction()">
The reason it doesn't work in Safari is because you're using attribute syntax. This will work fine though: script_tag.addEventListener('error', function(){/*...*/}, true); ...except in IE. If you want to check the script executed successfully, just set a variable using that script and check for it being set in the outer code.
This doesn't need jquery, doesn't need to load the script async, needs no timer nor to have the loaded script set a value. I've tested it in FF, Chrome, and Safari. <script> function loadScript(src) { return new Promise(function(resolve, reject) { let s = window.document.createElement("SCRIPT"); s.onload = () => resolve(s); s.onerror = () => reject(new Error(src)); s.src = src; // don't bounce to global handler on 404. s.addEventListener('error', function() {}); window.document.head.append(s); }); } let successCallback = (result) => { console.log(scriptUrl + " loaded."); } let failureCallback = (error) => { console.log("load failed: " + error.message); } loadScript(scriptUrl).then(successCallback, failureCallback); </script>
It was proposed to set a timeout and then assume load failure after a timeout. setTimeout(fireCustomOnerror, 4000); The problem with that approach is that the assumption is based on chance. After your timeout expires, the request is still pending. The request for the pending script may load, even after the programmer assumed that load won't happen. If the request could be canceled, then the program could wait for a period, then cancel the request.
This is how I used a promise to detect loading errors that are emited on the window object: <script type='module'> window.addEventListener('error', function(error) { let url = error.filename url = url.substring(0, (url.indexOf("#") == -1) ? url.length : url.indexOf("#")); url = url.substring(0, (url.indexOf("?") == -1) ? url.length : url.indexOf("?")); url = url.substring(url.lastIndexOf("/") + 1, url.length); window.scriptLoadReject && window.scriptLoadReject[url] && window.scriptLoadReject[url](error); }, true); window.boot=function boot() { const t=document.createElement('script'); t.id='index.mjs'; t.type='module'; new Promise((resolve, reject) => { window.scriptLoadReject = window.scriptLoadReject || {}; window.scriptLoadReject[t.id] = reject; t.addEventListener('error', reject); t.addEventListener('load', resolve); // Careful load is sometimes called even if errors prevent your script from running! This promise is only meant to catch errors while loading the file. }).catch((value) => { document.body.innerHTML='Error loading ' + t.id + '! Please reload this webpage.<br/>If this error persists, please try again later.<div><br/>' + t.id + ':' + value.lineno + ':' + value.colno + '<br/>' + (value && value.message); }); t.src='./index.mjs'+'?'+new Date().getTime(); document.head.appendChild(t); }; </script> <script nomodule>document.body.innerHTML='This website needs ES6 Modules!<br/>Please enable ES6 Modules and then reload this webpage.';</script> </head> <body onload="boot()" style="margin: 0;border: 0;padding: 0;text-align: center;"> <noscript>This website needs JavaScript!<br/>Please enable JavaScript and then reload this webpage.</noscript>
Well, the only way I can think of doing everything you want is pretty ugly. First perform an AJAX call to retrieve the Javascript file contents. When this completes you can check the status code to decide if this was successful or not. Then take the responseText from the xhr object and wrap it in a try/catch, dynamically create a script tag, and for IE you can set the text property of the script tag to the JS text, in all other browsers you should be able to append a text node with the contents to script tag. If there's any code that expects a script tag to actually contain the src location of the file, this won't work, but it should be fine for most situations.