Ah, asynchronous JavaScripts. Love 'em, hate 'em, but you gotta use them!
I have quite a few posts here on this blog about this stuff. Starting with something I considered an interesting hack to emulate PHP's require(). This was posted in 2005. (2005! That's ancient. That's only a year after gmail was introduced, and you know gmail has been around, like, always). Then there was this and this and this and this. These days dynamic SCRIPT tags are something common, mainly because of their non-blocking behavior that helps performance. This is all good.
I wanted to highlight the part where you load a script file and then execute a function once the file is done loading. A common, but kinda wrong pattern I've posted back at the time, and tweaked a little since, is like so:
var first_js = document.getElementsByTagName('script')[0]; var js = document.createElement('script'); js.src = file; // normal browsers js.onload = function () { alert('loaded!!'); }; // IE js.onreadystatechange = function () { if (js.readyState in {complete: 1, loaded: 1}) { alert('loaded!!'); } }; first_js.parentNode.insertBefore(js, first_js);
Back at the time (2006) this was fine. The problem is now that since version 9, IE supports onload
handler in script elements. But it also supports onreadystatechange
for backwards compatibility.
In other words in IE9+ your callbacks will be executed twice. Not good.
Single callback
There are various ways to deal with this situation.
1. You can delete the onload
callback in readystatechange
, beacuse readystatechange
is called first.
js.onreadystatechange = function () { if (js.readyState in {complete: 1, loaded: 1}) { callback(); js.onload = null; } };
2. You can use a single assignment to both
js.onload = js.onreadystatechange = function () { // stuff... js.onload = js.onreadystatechange = null; };
The problem with both of these is that readystatechange
is involved even in browsers that are modern (IE9+) and support onload
. Feels a bit ugh.
3. You can sniff onload
support
if (typeof js.onload !== 'undefined') { // good stuff.. } else { // onreadystatechange jazz }
This works because old IEs will not have any onload
property (hence undefined
) while supporting browsers will have this property initially set to null
.
Hmm, making a distinction between two falsy values null
and undefined
seems a little fragile. The next developer will come and say: "meh, what's with the typeof
verbosity, let's just say if (js.onload)
"... And the whole thing will fail.
4. (And this is my preferred method) is to sniff support using addEventListener
.
It just happens so that IE9, which supports onload
, is also the first IE browser that supports addEventListener
.
The whole thing looks like:
var first_js = document.getElementsByTagName('script')[0]; var js = document.createElement('script'); js.src = file; if (js.addEventListener) { // normal browsers js.addEventListener('load', function(){ alert('done!!'); }, false); } else { js.onreadystatechange = function() { // old IEs if (js.readyState in {loaded: 1, complete: 1}) { js.onreadystatechange = null; alert('done!!'); } }; } first_js.parentNode.insertBefore(js, first_js);
Drawback is that you decide on a feature (script onload
support) based on a different feature (addEventListener
support). I can live with this. We're talking here about an exception for known legacy browsers and shouldn't be an issue going forward in this brave new world where everyone lives in piece and love and brotherhood and sisterhood and motherhood and all browsers support onload
and addEventListener
.
So anyway, choose your poison 🙂
Here's a test page that listens to everything so you can play in different browsers:
https://www.phpied.com/files/jsasync/loaded.html
BTW, notice that IE is the only browser that fires window.onload
before the onload
of the (slow) script. This is another thing to keep in mind and look out for.
Comments? Find me on BlueSky, Mastodon, LinkedIn, Threads, Twitter