Prevent double callback execution in IE9

Important
After you read this article, please read the thread in the comments that starts with a response from Steve Souders. As it turns out, my client was using the code from Steve's EFWS book, first edition, published June 2009. That makes the code examples in the book almost 2 years old. Steve has updated his online Script Onload example page and that updated code does not result in the double execution in IE9.
I will update this article accordingly as soon as possible.

One of my clients sent me an email the other day about weird behaviour on his site: the Flash animation on the homepage looked weird in IE9. He was using Steve Souders' Script Onload technique for loading the SWFobject lib in a non-blocking way, and then executing some inline JavaScript as soon as SWFobject finished loading (the callback). Steve recommends this technique in his book Even Faster Web Sites and it always worked just fine in all browsers. But now there is IE9 and it fails. What is going on?

In short: IE9 supports both the load event and the readystatechange event. With the way Steve's Script Onload code is constructed, this results in double execution of the callback function in IE9. Luckily, there is an easy solution. Read on ...

Original code

For those who are unfamiliar with Steve's Script Onload code, here it is:

<script>
function callback() {
	// this is the callback function
	// executes after external script finishes loading
}

var script = document.createElement('script');
script.src = "http://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js"; 

script.onloadDone = false;
script.onload = function() { 
	script.onloadDone = true; 
	callback(); 
};
script.onreadystatechange = function() { 
	if (("loaded" === script.readyState || "complete" === script.readyState) && !script.onloadDone) {
		script.onloadDone = true; 
		callback();
	}}
document.getElementsByTagName('head')[0].appendChild(script);
</script>
You can see there are two event handlers. IE9 acts on both handlers and this results in the double execution. In this test case, the JPG image is loaded twice.
This waterfall chart (test page 1) clearly shows the double execution in IE9: Script Onload - Original - IE9 waterfall chart
View full test results on Webpagetest.org

Optimized code

The solution is simple, effective and future proof and you can read all about it in Nicholas Zakas' article Loading JavaScript without blocking. In summary:

<script>
function callback() {
	// this is the callback function
	// execute after external script finishes loading
}

var script = document.createElement('script');
script.src = "http://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js"; 

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);
</script>
Does this solve the problem? Yes, it does (test page 2):
Script Onload - Optimized - IE9 waterfall chart
View full test results on Webpagetest.org

Your feedback please!

I'm looking forward to receiving your questions and remarks.
Leave your comment below (now comment), send me a message via Twitter or via email: aaron {at} aaronpeters {dot} nl.

Like it? Share it!

Comments