Escaping the JavaScript call stack with setTimeout
Published by Jon November 26th, 2006 in Technology, Web, JavaScriptSetting up handlers that run after an event has fired is a tricky thing to do in JavaScript. Suppose you’re trying to work with text as it’s being entered by a user. As a silly example, suppose you wanted to have a <div> mirror the contents of a <textarea>. You might try something like this:
…but what you’d find is that the output <div> always lags behind the textarea by one keystroke. This is actually by design, and not a bug. The idea is that event handlers should have veto power over the event. By fiddling with the stopPropagation and preventDefault properties/functions, it’s possible to prevent the keystroke from being handled by the browser. It’s only after all of the handlers for an event have fired — and none of them have stopped the event — that the event actually takes effect.
The downside of the give-event-handlers-the-power-to-stop-events design is that it’s difficult to have a handler take effect after the event has already finished and been “accepted.” In the textarea-to-div mirroring example this isn’t a huge problem. There are plenty of workarounds. It’s possible, for example, to pull the character code out of the event, append that to the string from the textarea’s value property, and work from there. There are cases, though, where that’s not possible or not practical.
The situation, in a nutshell, is this: because the event doesn’t take effect until the event-handling call stack opens and closes completely, there’s no way to work in a post-event environment via conventional event handlers. Whew. It’s not possible (as near as I can tell) to circumvent the closing call stack requirement, so instead we have to work with it — or around it.
The trick I usually use it to call setTimeout with a delay of 0. For example, I’ll do something like this:
What that does is wait for the current call stack to close, then opens a new one in which the “real” event handler (outputDiv.innerHTML = textarea.value;) executes. Because the handler runs after the event-initiated call stack closes, the event has already taken place and textarea.value will be in a post-event state. Everything will work as originally intended, and the outputDiv will mirror whatever is currently in the textarea, rather than lagging one step behind.
This works because JavaScript is (at least in every implementation I’ve been able to get my hands on) not implemented as a multi-threaded language. If the JavaScript engine is busy when it’s time for something to happen (be it an event handler or a timer), it starts to queue up backlogged tasks rather than executing them in parallel. Once the current task (defined as the opening and closure of a single call stack) finishes, a new call stack is opened and the next task begins. (Side note: my test code for this is a mess, but I’d be happy to clean it up and publish it if there’s interest).
What happens when we call something using setTimeout with a delay of 0 is that the JavaScript engine notices that it’s busy (with whatever task called setTimeout) and queues the setTimeout code for execution immediately after the current call stack closes. In other words, it calls it as soon as possible in its own call stack.
I’ve found this technique to be useful in a few cases, and I’m sure there are more. Here are a handful of the things I use it for:
- As mentioned above, I’ll often use
setTimeout(..., 0)to set up post-event handlers. - Most browsers are smart enough not to apply DOM changes until a call stack closes. For example, if a function changed an object’s color from red to green and then back to red, the browser would be smart enough to realize that there wasn’t any net change and that it shouldn’t really do anything. I’ve found a few browser bugs, though, where it’s necessary to “poke” the DOM to make things appear correctly (particularly when scrolling with fixed-position elements). My poking functions change the style one way with one call stack, and then immediately revert the change in another call stack, thereby forcing the browser to redraw the problem element.
- Along the same lines, there are some things that can’t be done in a single call stack. I’ve heard arguments that that’s the case for security reasons, but I don’t buy it. As an example, I’ve had trouble programmatically spawning form fields with content and then focusing/selecting those fields in the same call stack. Using the
setTimeouttrick, everything works just fine.
There’s something of a black art to knowing when to use the setTimeout trick. If you’ve ever found yourself in a situation where you can make something happen in response to user actions, but can’t make it happen in code, chances are that you need to escape the call stack somewhere. As a rule, though, it doesn’t (shouldn’t) happen often. I only use it in three or four places in Thinkature, which is a very substantial piece of JavaScript.
No Responses to “Escaping the JavaScript call stack with setTimeout”
Please Wait
Leave a Reply