in Web

How setTimeout’s timer clamping works

This is a fantastic post on the blink-dev discussion list by Chrome dev James Robinson describing how setTimeout’s timer clamping works. James argues that setTimeout(fn, 0) behaves exactly as setImmediate(fn) would except in excessively-nested timers.

Nearly everything I’ve read starts off with an incorrect idea about how timer clamping works or why it’s in place.  The way timer clamping works [1] is every task has an associated timer nesting level.  If the task originates from a setTimeout() or setInterval() call, the nesting level is one greater than the nesting level of the task that invoked setTimeout() or the task of the most recent iteration of that setInterval(), otherwise it’s zero.  The 4ms clamp only applies once the nesting level is 4 or higher.  Timers set within the context of an event handler, animation callback, or a timer that isn’t deeply nested are not subject to the clamping.  This detail hasn’t been reflected in the HTML spec until recently (and there’s an off-by-one error in it right now), but has been true in WebKit/Blink since 2006 [2] and in Gecko since 2009 [3].  The practical effect of this is that setTimeout(…, x) means exactly what you think it would even for x in [0, 4) so long as the nesting level isn’t too high.

With a better understanding of timer clamping, let’s consider the possible use cases for scheduling asynchronous work.  One is to time work relative to display updates.  Obviously requestAnimationFrame() is the right API to use for animations, but it’s also the right choice for one-off uses.  To perform work immediately before the next display – for example to batch up graphical updates as data is streamed in off the network – just make a one-off requestAnimationFrame() call.  To perform work immediately -after- a display, just call setTimeout() from inside the requestAnimationFrame() handler.  The nesting level is zero within a rAF() callback so the timeout parameter will not be clamped to 4.

  • jerryj

    Yes, a very detailed post by James Robinson and very informative…. But wrong…

    1. The last time Chrome worked as described by James Robinson was in 2008, when a change was made to Chrome that caused clamping to break. Chrome has not been fixed since.

    2. setTimeout(fn, 0) behaves like setTimeout(fn, 1), which is not at all like setImmediate(fn). The source of this information is actual testing, and Chrome’s own source code. Notice the ‘max’ at https://code.google.com/p/chromium/codesearch#chromium/src/third_party/WebKit/Source/core/frame/DOMTimer.cpp&l=93, so there is no ‘0’ allowed in timeouts in Chrome.