Thursday, March 25th, 2010

setTimeout: how to get the shortest delay

<p>We have all been there with our setTimeout(func, 0), but how close to 0 does it get?

The intrepid David Baron of Mozilla delves into the delay in setTimeout and comes out with some interesting results. Here is his story:

On Sunday, somebody with the nickname {g} was on irc.mozilla.org asking about the behavior of setTimeout.
In particular, he wanted to divide up work into a bunch of pieces in a way that allowed the user to interact with the page while the work was happening, and was doing this by doing a piece of the work, and then making a setTimeout call to continue the work. (In some cases, this could also be done using workers.) Unfortunately for him, setTimeout in most browsers doesn’t allow a delay less than about 10 milliseconds (it forces any smaller delays to be longer), so the work wasn’t finishing as fast as it could. (Chrome has changed this to 2 milliseconds, though, and apparently had some problems with it.)

A while ago, Jeff Walden suggested to me that Web pages could get the equivalent of setTimeout, with a real zero delay, using postMessage. This turns out to be relatively straightforward:

javascript
< view plain text >
  1. // Only add setZeroTimeout to the window object, and hide everything
  2.     // else in a closure.
  3.     (function() {
  4.         var timeouts = [];
  5.         var messageName = "zero-timeout-message";
  6.  
  7.         // Like setTimeout, but only takes a function argument.  There's
  8.         // no time argument (always zero) and no arguments (you have to
  9.         // use a closure).
  10.         function setZeroTimeout(fn) {
  11.             timeouts.push(fn);
  12.             window.postMessage(messageName, "*");
  13.         }
  14.  
  15.         function handleMessage(event) {
  16.             if (event.source == window &amp;&amp; event.data == messageName) {
  17.                 event.stopPropagation();
  18.                 if (timeouts.length > 0) {
  19.                     var fn = timeouts.shift();
  20.                     fn();
  21.                 }
  22.             }
  23.         }
  24.  
  25.         window.addEventListener("message", handleMessage, true);
  26.  
  27.         // Add the one thing we want added to the window object.
  28.         window.setZeroTimeout = setZeroTimeout;
  29.     })();

I wrote a demo page that demonstrates that this is significantly faster than setTimeout(0).

On Linux

On a Firefox nightly 100 iterations of setZeroTimeout take about 10-20 milliseconds most of the time, but occasionally longer; on a WebKit build I have it takes about 4-6 milliseconds, but occasionally a bit longer. (We should probably investigate the performance difference here.) In comparison, in Firefox and on non-Chromium-based WebKit, the setTimeout version takes about a second (though perhaps even longer on Windows).

On Mac: Boris tells me that on Mac, it’s the opposite: Gecko is faster than Safari or Chrome.

Related Content:

7 Comments »

Comments feed TrackBack URI

The way I got around setTimeout not being fast enough is to use a few of them at a time with an offset to each other. Solves the problem, at least for me, and it’s far easier/cleaner than 20+ lines of code.
`
ie.
setTimeout(func, 13);
setTimeout(func, 16);
setTimeout(func, 19);

Comment by ck2 — March 25, 2010

20 lines of code written once and never needed to be looked at again is easy and clean. I like it. My only concern – was the mandatory 10ms actually doing us a favour? In other words could the timeout gap get too small to allow for interruption
.
(You know you’re getting old when you have to think about the answer to the spam question)

Comment by AngusC — March 25, 2010

ck2′s way is backwards compatible and can be made reusable as well so you don’t have to look at it either. In fact you can make it more reliable as well by doing some min timeout tests per browser environment and create n setTimout calls inside the function wrapper with a calculated offset to ensure a reliable interval.
.
Hmm, TO THE CODEMOBILE!

Comment by BenGerrissen — March 25, 2010

What exactly is the purpose of a truly 0ms timeout? Why not just use web workers if we’re dealing with threading? postMessage isnt cross browser anyways.

I could understand trying to create a speedier setTimeout that sneaks in under 10ms, but if we’re talking 0ms we’re talking webworkers amirite?

Comment by jlukic — March 25, 2010

For those MooToolians who want to simulate threading in loops, you can try this plugin: http://mootools.net/forge/p/threaded_loop

Comment by sixtyseconds — March 25, 2010

Could this be accomplished in a more browser compatible way using another event handler? Say, for example, the focus event on a hidden input element that instantly blurs itself?

Comment by Chiper — March 25, 2010

Maybe I’m missing something, but I don’t think you quite understand the point of setTimeout(fn, 0). The normal setTimeout(fn,x) behavior is wait x ms then call fn, but setTimeout(fn, 0) is a special case. When the browser encounters a call like this, it does not run it as quickly as possible as you may be expecting. In fact, it does something that’s almost the opposite. setTimeout(fn,0) is like telling the browser to place fn at the very bottom of the call stack and make it the very last thing that gets run (I’m sure you know javascript is single-threaded). This is useful in cases where you have multiple plugins and you need to make sure your code gets run last, or you’re trying to debug a value and you want to effect or log it’s value at multiple places on the run stack. Or perhaps you want to simulate a progress bar in javascript and you need javascript to update the page before it finishes running. If you just want to run a function as quickly as possible, use setTimeout(fn, 1). Now, you might as well just say setTimeout(fn, 10) because most things take 10ms anyway (even slower in IE and others). So, doesn’t it make sense now why setTimeout(fn, 10) is faster than setTimeout(fn, 0)? So, in this case (I’m not sure about 1-9), the browser is not forcing a delay of 0ms to be longer. It’s actually doing something different but useful and doesn’t even care about how long before the javascript stack actually gets to the function you want to run. Hope that helps. By the way, this is completely cross-browser and has worked in all browsers for a long time. It is native to javascript, not really the browser, so please forgive my simplistic explanation. So be careful when using a 0ms timeout. It may not be doing what you think it is.

Comment by Timmyw — March 27, 2010

Leave a comment

You must be logged in to post a comment.