I provide a JavaScript widget to several web sites, which they load asynchronously. My widget in turn needs to load a script provided by another party, outside my control.
There are several ways to check whether that script has successfully loaded. However, I also need to run different code if that script load has failed.
The obvious tools that don't work include:
I'm not willing to use JavaScript libraries, such as jQuery. I need a very small script to minimize my impact on the sites that use my widget.
I want to detect the failure as soon as possible, so using a timer to poll it is undesirable. I wouldn't mind using a timer as a last resort on old browsers, though.
I've found the <script>
tag's onerror
event to be unreliable in some major browsers. (It seemed to depend on which add-ons were installed.)
Anything involving document.write
is right out. (Besides that method being intrinsically evil, my code is loaded asynchronously so document.write
may do bad things to the page.)
I had a previous solution that involved loading the <script>
in a new <iframe>
. In that iframe, I set a <body onload=...>
event handler that checked whether the <script onload=...>
event had already fired. Because the <script>
was part of the initial document, not injected asynchronously later, onload
only fired after the network layer was done with the <script>
tag.
However, now I need the script to load in the parent document; it can't be in an iframe any more. So I need a different way to trigger code as soon as the network layer has given up trying to fetch the script.
I read "Deep dive into the murky waters of script loading" in an attempt to work out what ordering guarantees I can count on across browsers.
If I understand the techniques documented there:
.js
file.<script async>
attribute to false via the DOM,<script onreadystatechange=...>
on IE 6+.Despite looking at the async support table, I can't tell whether I can rely on script ordering in enough browsers for this to be feasible.
So how can I reliably handle failure during loading of a script I don't control?
I believe I've solved the question I asked, though it turns out this doesn't solve the problem I actually had. Oh well. Here's my solution:
We want to run some code after the browser finishes attempting to load a third-party script, so we can check whether it loaded successfully. We accomplish that by constraining the load of a fallback script to happen only after the third-party script has either run or failed. The fallback script can then check whether the third-party script created the globals it was supposed to.
Cross-browser in-order script loading inspired by http://www.html5rocks.com/en/tutorials/speed/script-loading/.
var fallbackLoader = doc.createElement(script),
thirdPartyLoader = doc.createElement(script),
thirdPartySrc = '<URL to third party script>',
firstScript = doc.getElementsByTagName(script)[0];
// Doesn't matter when we fetch the fallback script, as long as
// it doesn't run early, so just set src once.
fallbackLoader.src = '<URL to fallback script>';
// IE starts fetching the fallback script here.
if('async' in firstScript) {
// Browser support for script.async:
// http://caniuse.com/#search=async
//
// By declaring both script tags non-async, we assert
// that they need to run in the order that they're added
// to the DOM.
fallbackLoader.async = thirdPartyLoader.async = false;
thirdPartyLoader.src = thirdPartySrc;
doc.head.appendChild(thirdPartyLoader);
doc.head.appendChild(fallbackLoader);
} else if(firstScript.readyState) {
// Use readyState for IE 6-9. (IE 10+ supports async.)
// This lets us fetch both scripts but refrain from
// running them until we know that the fetch attempt has
// finished for the first one.
thirdPartyLoader.onreadystatechange = function() {
if(thirdPartyLoader.readyState == 'loaded') {
thirdPartyLoader.onreadystatechange = null;
// The script-loading tutorial comments:
// "can't just appendChild, old IE bug
// if element isn't closed"
firstScript.parentNode.insertBefore(thirdPartyLoader, firstScript);
firstScript.parentNode.insertBefore(fallbackLoader, firstScript);
}
};
// Don't set src until we've attached the
// readystatechange handler, or we could miss the event.
thirdPartyLoader.src = thirdPartySrc;
} else {
// If the browser doesn't support async or readyState, we
// just won't worry about the case where script loading
// fails. This is <14% of browsers worldwide according to
// caniuse.com, and hopefully script loading will succeed
// often enough for them that this isn't a problem.
//
// If that isn't good enough, you might try setting an
// onerror listener in this case. That still may not work,
// but might get another small percentage of old browsers.
// See
// http://blog.lexspoon.org/2009/12/detecting-download-failures-with-script.html
thirdPartyLoader.src = thirdPartySrc;
firstScript.parentNode.insertBefore(thirdPartyLoader, firstScript);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With