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()
?
One reason to do that is to defer the execution of code to a separate, subsequent event loop. When responding to a browser event of some kind (mouse click, for example), sometimes it's necessary to perform operations only after the current event is processed. The
setTimeout()
facility is the simplest way to do it.edit now that it's 2015 I should note that there's also
requestAnimationFrame()
, which isn't exactly the same but it's sufficiently close tosetTimeout(fn, 0)
that it's worth mentioning.Some other cases where setTimeout is useful:
You want to break a long-running loop or calculation into smaller components so that the browser doesn't appear to 'freeze' or say "Script on page is busy".
You want to disable a form submit button when clicked, but if you disable the button in the onClick handler the form will not be submitted. setTimeout with a time of zero does the trick, allowing the event to end, the form to begin submitting, then your button can be disabled.
Preface:
IMPORTANT NOTE: While it's most upvoted and accepted, the accepted answer by @staticsan actually is NOT CORRECT! - see David Mulder's comment for explanation why.
Some of the other answers are correct but don't actually illustrate what the problem being solved is, so I created this answer to present that detailed illustration.
As such, I am posting a detailed walk-through of what the browser does and how using
setTimeout()
helps. It looks longish but is actually very simple and straightforward - I just made it very detailed.UPDATE: I have made a JSFiddle to live-demonstrate the explanation below: http://jsfiddle.net/C2YBE/31/ . Many thanks to @ThangChung for helping to kickstart it.
UPDATE2: Just in case JSFiddle web site dies, or deletes the code, I added the code to this answer at the very end.
DETAILS:
Imagine a web app with a "do something" button and a result div.
The
onClick
handler for "do something" button calls a function "LongCalc()", which does 2 things:Makes a very long calculation (say takes 3 min)
Prints the results of calculation into the result div.
Now, your users start testing this, click "do something" button, and the page sits there doing seemingly nothing for 3 minutes, they get restless, click the button again, wait 1 min, nothing happens, click button again...
The problem is obvious - you want a "Status" DIV, which shows what's going on. Let's see how that works.
So you add a "Status" DIV (initially empty), and modify the
onclick
handler (functionLongCalc()
) to do 4 things:Populate the status "Calculating... may take ~3 minutes" into status DIV
Makes a very long calculation (say takes 3 min)
Prints the results of calculation into the result div.
Populate the status "Calculation done" into status DIV
And, you happily give the app to users to re-test.
They come back to you looking very angry. And explain that when they clicked the button, the Status DIV never got updated with "Calculating..." status!!!
You scratch your head, ask around on StackOverflow (or read docs or google), and realize the problem:
The browser places all its "TODO" tasks (both UI tasks and JavaScript commands) resulting from events into a single queue. And unfortunately, re-drawing the "Status" DIV with the new "Calculating..." value is a separate TODO which goes to the end of the queue!
Here's a breakdown of the events during your user's test, contents of the queue after each event:
[Empty]
[Execute OnClick handler(lines 1-4)]
[Execute OnClick handler(lines 2-4), re-draw Status DIV with new "Calculating" value]
. Please note that while the DOM changes happen instantaneously, to re-draw the corresponding DOM element you need a new event, triggered by the DOM change, that went at the end of the queue.[Execute OnClick handler(lines 3-4), re-draw Status DIV with "Calculating" value]
.[Execute OnClick handler(line 4), re-draw Status DIV with "Calculating" value, re-draw result DIV with result]
.[Execute OnClick handler, re-draw Status DIV with "Calculating" value, re-draw result DIV with result; re-draw Status DIV with "DONE" value]
.return
fromonclick
handler sub. We take the "Execute OnClick handler" off the queue and start executing next item on the queue.So, the underlying problem is that the re-draw event for "Status" DIV is placed on the queue at the end, AFTER the "execute line 2" event which takes 3 minutes, so the actual re-draw doesn't happen until AFTER the calculation is done.
To the rescue comes the
setTimeout()
. How does it help? Because by calling long-executing code viasetTimeout
, you actually create 2 events:setTimeout
execution itself, and (due to 0 timeout), separate queue entry for the code being executed.So, to fix your problem, you modify your
onClick
handler to be TWO statements (in a new function or just a block withinonClick
):Populate the status "Calculating... may take ~3 minutes" into status DIV
Execute
setTimeout()
with 0 timeout and a call toLongCalc()
function.LongCalc()
function is almost the same as last time but obviously doesn't have "Calculating..." status DIV update as first step; and instead starts the calculation right away.So, what does the event sequence and the queue look like now?
[Empty]
[Execute OnClick handler(status update, setTimeout() call)]
[Execute OnClick handler(which is a setTimeout call), re-draw Status DIV with new "Calculating" value]
.[re-draw Status DIV with "Calculating" value]
. The queue has nothing new in it for 0 more seconds.[re-draw Status DIV with "Calculating" value, execute LongCalc (lines 1-3)]
.[execute LongCalc (lines 1-3)]
. Please note that this re-draw event might actually happen BEFORE the alarm goes off, which works just as well.Hooray! The Status DIV just got updated to "Calculating..." before the calculation started!!!
Below is the sample code from the JSFiddle illustrating these examples: http://jsfiddle.net/C2YBE/31/ :
HTML code:
JavaScript code: (Executed on
onDomReady
and may require jQuery 1.9)setTimeout()
buys you some time until the DOM elements are loaded, even if is set to 0.Check this out: setTimeout
Most browsers have a process called main thread, that is responsible for execute some JavaScript tasks, UI updates e.g.: painting, redraw or reflow, etc.
Some JavaScript execution and UI update tasks are queued to the browser message queue, then are dispatched to the browser main thread to be executed.
When UI updates are generated while the main thread is busy, the tasks are added into the message queue.
setTimeout(fn, 0);
add thisfn
to the end of the queue to be executed. It schedules a task to be added on the message queue after a given amount of time.Since it is being passed a duration of
0
, I suppose it is in order to remove the code passed to thesetTimeout
from the flow of execution. So if it's a function that could take a while, it won't prevent the subsequent code from executing.