Daniel Lew Daniel Lew - 2 months ago 4
Javascript Question

Why is setTimeout(fn, 0) sometimes useful?

I've recently run into a rather nasty bug, wherein the code was loading a

<select>
dynamically via JavaScript. This dynamically loaded
<select>
had a pre-selected value. In IE6, we already had code to fix the selected
<option>
, because sometimes the
<select>
's
selectedIndex
value would be out of sync with the selected
<option>
's
index
attribute, as below:

field.selectedIndex = element.index;


However, this code wasn't working. Even though the field's
selectedIndex
was being set correctly, the wrong index would end up being selected. However, if I stuck an
alert()
statement in at the right time, the correct option would be selected. Thinking this might be some sort of timing issue, I tried something random that I'd seen in code before:

var wrapFn = (function() {
var myField = field;
var myElement = element;

return function() {
myField.selectedIndex = myElement.index;
}
})();
setTimeout(wrapFn, 0);


And this worked!

I've got a solution for my problem, but I'm uneasy that I don't know exactly why this fixes my problem. Does anyone have an official explanation? What browser issue am I avoiding by calling my function "later" using
setTimeout()
?

Answer

This works because you're doing co-operative multi-tasking.

A browser has to do a number of things pretty much all at once, and just one of those is execute JavaScript. But one of the things JavaScript is very often used for is to ask the browser to build a display element. This is often assumed to be done synchronously (particularly as JavaScript is not executed in parallel) but there is no guarantee this is the case and JavaScript does not have a well-defined mechanism for waiting.

The solution is to "pause" the JavaScript execution to let the rendering threads catch up. And this is the effect that setTimeout() with a timeout of 0 does. It is like a thread/process yield in C. Although it seems to say "run this immediately" it actually gives the browser a chance to finish doing some non-JavaScript things that have been waiting to finish before attending to this new piece of JavaScript.

(In actuality, setTimeout() re-queues the new JavaScript at the end of the execution queue. See the comments for links to a longer explanation.)

IE6 just happens to be more prone to this error, but I have seen it occur on older versions of Mozilla and in Firefox.