If you’ve done any amount of non-trivial JavaScript coding then you have probably come across the ‘setTimeout()‘ function before. In case you haven’t, this function takes a JavaScript expression and evaluates/executes it after a set delay (specified in milliseconds). For instance:
setTimeout("alert('test')", 5000);
…will display an alert box containing the text “test” after a 5-second delay. Simple enough. But what you may not know is that ‘setTimeout()‘ also supports an alternate and much more useful usage mode where instead of literal javascript text you can pass a function reference/closure and a variable number of parameters, like so:
setTimeout(alert, 5000, "test");
This will produce the same results as the first call, but the syntax is much more readable and much less prone to errors when coding. Why this variant is barely even mentioned in the documentation I cannot say, but anyone who has worked with Flash/ActionScript is probably familiar with this syntax.
There is one major limitation to this approach, however; it does not work correctly in Internet Explorer. That’s a pretty serious drawback, considering Internet Explorer’s market share. But we can fix it by overriding the default ‘setTimeout()‘ implementation as follows:
window._oldSetTimeout = window.setTimeout;
window.setTimeout = function(closureOrText, delay) {
if (arguments.length <= 2 || typeof closureOrText != "function") {
_oldSetTimeout(closureOrText, delay);
}
else {
var funcArgs = new Array();
for (var index = 2; index < arguments.length; index++) {
funcArgs.push(arguments[index]);
}
_oldSetTimeout(_timeoutCallback(closureOrText, funcArgs), delay);
}
};
window._timeoutCallback = function(closure, argArray) {
return function() {
closure.apply(this, argArray);
};
};
Now the behavior will be consistent between Internet Explorer and other browsers. Note that although the default implementation only needs to be overridden in Internet Explorer, the above code will work correctly in other browsers as well. Also note that there is one more caveat to be aware of here as well. In Internet Explorer, functions provided by the system (such as ‘alert()‘, ‘escape()‘, ‘parseInt()‘, etc.) are of a different type than user-defined functions. Now this wouldn’t be a huge problem, except that whatever type Internet Explorer uses for its system functions does not support the ‘apply()‘ method (thanks, Microsoft).
So even with the above code, if you use ‘setTimeout()‘ with a system function in Internet Explorer, you will still get incorrect behavior. We can fix this by revising the code as follows:
window._oldSetTimeout = window.setTimeout;
window.setTimeout = function(closureOrText, delay) {
var funcArgs = new Array();
for (var index = 2; index < arguments.length; index++) {
funcArgs.push(arguments[index]);
}
if (arguments.length <= 2 || typeof closureOrText != "function") {
if (arguments.length <= 2 || typeof closureOrText == "string") {
_oldSetTimeout(closureOrText, delay);
}
else {
//hack for IE system functions
_oldSetTimeout(_timeoutCallbackForSystemFunction (closureOrText, funcArgs), delay);
}
}
else {
_oldSetTimeout(_timeoutCallback(closureOrText, funcArgs), delay);
}
};
window._timeoutCallback = function(closure, argArray) {
return function() {
closure.apply(this, argArray);
};
};
window._timeoutCallbackForSystemFunction = function(closure, argArray) {
return function() {
if (argArray.length == 1) {
closure(argArray[0]);
}
else if (argArray.length == 2) {
closure(argArray[0], argArray[1]);
}
else {
alert("WARN: Too many arguments passed to system function; timeout callback not executed!");
}
};
};
This code will produce the correct behavior in Internet Explorer, and is still compatible with all other major browsers. In Internet Explorer it is limited to supporting system functions with a maximum of 2 parameters, but I’m not aware of any that require more than that anyways. An alternate workaround is to define your own function that wraps the system function, and then use the wrapper function in the ‘setTimeout()‘ call, like so:
var myAlert = function(text) {
alert(text);
};
setTimeout(myAlert, "5000", "test");
It’s a bit less convenient to do it this way if you will be working with several different system functions, but this approach will also yield correct behavior in Internet Explorer (and is compatible with other browsers as well).
In any case, either approach can be used to get the closure-based ‘setTimeout()‘ syntax working consistently across all major browsers. And once you have that, there is very little reason to ever prefer the text-based version.