NCZOnline

Script yielding with setImmediate

Those who have attended my talks on JavaScript performance are familiar with my propensity for using setTimeout() to break up long scripts into smaller chunks. When using setTimeout(), you’re changing the time at which certain code is executed, effectively yielding the UI thread to perform the already-queued tasks. For example, you can instruct some code to be added to the UI thread queue after 50ms via:

setTimeout(function(){
//do something
}, 50)

So after 50ms, this function is added to the queue and it’s executed as soon as its turn comes. A call to setTimeout() effectively allows the current JavaScript task to complete so the next UI update can occur.

Problems

Even though I’ve been a big proponent of using setTimeout() in this way, there are a couple of problems with this technique. First and foremost, timer resolution across browsers varies. Internet Explorer 8 and earlier have a timer resolution of 15.6ms while Internet Explorer 9 and later as well as Chrome have a timer resolution of 4ms. All browsers enforce a minimum delay for setTimeout(), so setTimeout(fn, 0) actually execute after 0ms, it executes after the timer resolution.

Another problem is power usage. Managing timers drains laptop and mobile batteries. Chrome experimented with lowering the timer resolution to 1ms before finding that it was hurting battery life on laptops. Ultimately, the decision was made to move back to a 4ms timer resolution. Other browsers have since followed suit, though many throttle timer resolution to 1s for background tabs. Microsoft found that lowering the timer resolution to 1ms can reduce battery run time by 25%. Internet Explorer 9, in fact, keeps timer resolution at 15.6ms when a laptop is running on battery and only increases to 4ms when plugged in.

The setImmediate() function

The Efficient Script Yielding specification from the W3C Web Performance Working Group defines a new function for achieving this breaking up of scripts called setImmediate(). The setImmediate() function accepts a single argument, which is a function to execute, and it inserts this function to be executed as soon as the UI thread is idle. Basic usage:

var id = setImmediate(function(){
//do something
});

The setImmediate() function returns an ID that can be used to cancel the call via clearImmediate() if necessary.

It’s also possible to pass arguments into the setImmediate() function argument by including them at the end:

setImmediate(function(doc, win){
//do something
}, document, window);

Passing additional arguments in this way means that you needn’t always use a closure with setImmediate() in order to have useful information available to the executing function.

Advantages

What setImmediate() does is free the browser from needing to manage a timer for this process. Instead of waiting for a system interrupt, which uses more power, the browser can simply wait for the UI queue to empty and then insert the new JavaScript task. Node.js developers may recognize this functionality since process.nextTick() does the same thing in that environment.

Another advantage is that the specified function executes after a much smaller delay, without a need to wait for the next timer tick. That means the entire process completes much faster than with using setTimeout(fn, 0).

Browser support

Currently, only Internet Explorer 10 supports setImmediate(), and it does so through msSetIntermediate() since the specification is not yet finalized. The Internet Explorer 10 Test Drive site has a setImmediate() example that shows the improved performance using the new method. The example sorts values using a delay while the current state of the sort is displayed visually. This example requires Internet Explorer 10.

The future

I’m very optimistic about the setImmediate() function and its value to web developers. Using timers for script yielding is a hack, for sure, and having an official way to yield scripts is a huge win for performance. I hope that other browsers quickly pick up on the implementation so we can start using this soon.

Disclaimer: Any viewpoints and opinions expressed in this article are those of Nicholas C. Zakas and do not, in any way, reflect those of my employer, my colleagues, Wrox Publishing, O'Reilly Publishing, or anyone else. I speak only for myself, not for them.

Related Posts

Further reading

11 Comments

At work we made an open-source implementation of setImmediate/clearImmediate that can be used today, cross-browser. It uses msSetImmediate if available, then falls back to postMessage, then to setTimeout.

@Domenic – Nice, I was going to write a followup about using postMessage() and compare performance.

@Vladmir – No, this is very, very different. requestAnimationFrame() tells the browser to schedule a paint event and then fires the callback before that paint event happens. setImmediate() fires when all paint events have completed.

- reqAnimFrame schedules a paint event (effectively happening after all other paint events in the queue are complete)
- all previous pain events complete
- reqAnimFrame callback is fired (which triggers a paint event)
- as there are two subsequent paint events to happen now (the scheduled one and the callback-triggered one), they effectively happen as one in batch

I used this technique in my library (http://leaflet.cloudmade.com/) and made panning MUCH smoother on browsers that support reqAnimFrame. So it seems to me that requestAnimationFrame can be used to solve the same problem as setImmediate. Am I right?

I think I see the difference and it’s subtle. requestAnimationFrame works on animation ticks which are around 1/60 a second, AND implies a repaint so it is meant for a situation like panning (explains why Vladimir’s use case works with requestAnimationFrame)

But setImmediate can call a function even faster than whatever the browser’s ‘tick’ resolution happens to be, and if you’re not planning an updating UI with that function, it’s doubly more performant.

What surprises me is that last bit – the UI thread really finishes in under 1-4 ms? Is that common or just an extreme example? It’s nice that this function, in either case, manages that for you.

@Siderite – No, this would be a dramatic change to how setTimeout(0) works, and that could potentially break functionality. Believe it or not, people actually do rely on the delay rounding that browsers currently do, and changing that would be perilous.

-1 then?
Jokes aside, I have used setTimeout and setInterval a lot and I often had to use a construct like function myTimeout(func,interval,rep) {
var f=rep?setInterval:setTimeout;
return f(func,interval);
} with more complicated code for caching the timeout/interval values so they can all be cleared, etc. now we add another function that looks exactly the same, only it has another separate set of set/clear functions and token types. Reminds me of PHP and its many functions, when the power of javascript was it was simple, but flexible.