I have a block of JavaScript code running on my page; let's call it func1. It takes several milliseconds to run. While that code is running, the user may click, move the mouse, enter some keyboard input, etc. I have another block of code, func2, that I want to run after all of those queued-up input events have resolved. That is, I want to ensure the order:
func1- All handlers bound to input events that occurred while
func1was running func2
My question is: Is calling setTimeout func2, 0 at the end of func1 sufficient to guarantee this ordering, across all modern browsers? What if that line came at the beginning of func1—what order should I expect in that case?
Please back up your answers with either references to the relevant specs, or test cases.
Update: It turns out that no, it's not sufficient. What I failed to realize in my original question was that input events aren't even added to the queue until the current code block has been executed. So if I write
// time-consuming loop...
setTimeout func2, 0
then only after that setTimeout is run will any input events (clicks, etc.) that occurred during the time-consuming loop be queued. (To test this, note that if you remove, say, an onclick callback immediately after the time-consuming loop, then clicks that happened during the loop won't trigger that callback.) So func2 is queued first and takes precedence.
Setting a timeout of 1 seemed to work around the issue in Chrome and Safari, but in Firefox, I saw input events resolving after timeouts as high as 80 (!). So a purely time-based approach clearly isn't going to do what I want.
Nor is it sufficient to simply wrap one setTimeout ... 0 inside of another. (I'd hoped that the first timeout would fire after the input events queued, and the second would fire after they resolved. No such luck.) Nor did adding a third, or a fourth, level of nesting suffice (see Update 2 below).
So if anyone has a way of achieving what I described (other than setting a timeout of 90+ milliseconds), I'd be very grateful. Or is this simply impossible with the current JavaScript event model?
Here's my latest JSFiddle testbed: http://jsfiddle.net/EJNSu/7/
Update 2: A partial workaround is to nest func2 inside of two timeouts, removing all input event handlers in the first timeout. However, this has the unfortunate side effect of causing some—or even all—input events that occurred during func1 to fail to resolve. (Head to http://jsfiddle.net/EJNSu/10/ and try rapidly clicking the link several times to observe this behavior. How many clicks does the alert tell you that you had?) So this, again, surprises me; I wouldn't think that calling setTimeout func2, 0, where func2 sets onclick to null, could prevent that callback from being run in response to a click that happened a full second ago. I want to ensure that all input events fire, but that my function fires after them.
Update 3: I posted my answer below after playing with this testbed, which is illuminating: http://jsfiddle.net/TrevorBurnham/uJxQB/
Move the mouse over the box (triggering a 1-second blocking loop), then click multiple times. After the loop, all the clicks you performed play out: The top box's click handler flips it under the other box, which then receives the next click, and so on. The timeout triggered in the mouseenter callback does not consistently occur after the click events, and the time it takes for the click events to occur varies wildly across browsers even on the same hardware and OS. (Another odd thing this experiment turned up: I sometimes get multiple jQuery mouseenter events even when I move the mouse steadily into the box. Not sure what's going on there.)