Speed up your Websites with a Faster setTimeout using soon()

January 7, 2015

In this post, I talk about:

  • How setTimeout incurs a delay cost, even when the delay is 0ms
  • How some browsers/developers have tried to remedy this problem.
  • How you can squeeze out the maximum performance using my soon() function instead.
  • When to use soon() and when not to.

The setTimeout hidden delay

If you have done much asynchronous programming in Javascript, you may have found yourself wanting to defer the running of some code until after the currently executing code was finished. You most likely used something like this:

// some code to execute before phase 2
setTimeout(phase2, 0);
// some more code to execute before phase 2

But did you realize the potential cost you were paying in terms of performance?  Modern browsers impose a minimum delay of 4ms on every setTimeout, regardless of how long you specify. This is referred to as "clamping" (and it used to be 15ms before being reduced to 4ms - but don't expect it to drop any more - 4ms is now official).  It is particularly apparent when contained within a code loop, animation or any oft-repeated bit of code (such as a drag event handler).

The setImmediate Alternative

Back in 2011 Microsoft proposed a solution to the setTimeout delay problem by implementing a new function called setImmediate. This would be used in the same fashion as setTimeout, but with an assumed (and adhered to) 0ms time delay. 

Currently, only MSIE 10+ (and Node 0.9+) support setImmediate in spite of some vocal calls to other browser makers to follow suit. 

This has in turn spawned the creation of polyfills to attempt as closely as possible to implement this missing feature in browsers that don't support it.  The YuzuJS project is a well regarded example.

Even faster : Lightning Fast Asynchronous Execution 

But if you are really looking to efficiently yield the thread and queue your code to be executed immediately after the current thread is finished, I have a much smaller, and better solution - I call it soon().

In pursuit of the fastest script yielding possible, I utilized a MutationObserver, which is a well supported feature that allows you to monitor DOM nodes for changes and executes code as soon as the main thread is able - which makes it the perfect candidate for a fast yield. [pardon the oxymoron]

Additionally, once code is executing that has been yielded with soon(), if that code yields another function with another soon() call (which is quite common), there is no need to employ any "external flow control" – The current thread was executing by our soon() - and as such, we can simply queue any further soon() requests. This technique of "mainlining" successive soon() calls means that any performance hit incurred by the internal yield mechanism is now completely gone.

The script source is available here on GitHub as a gist. If you want a quick, grab & dash compressed version, here's the whole thing:

var soon=(function(){var c=[];function b(){while(c.length){var d=c[0];d.f.apply(d.m,d.a);c.shift()}}var a=(function(){if(typeof MutationObserver!=="undefined"){var d=document.createElement("div");return function(e){var f=new MutationObserver(function(){f.disconnect();e()});f.observe(d,{attributes:true});d.setAttribute("a",0)}}if(typeof setImmediate!=="undefined"){return setImmediate}return function(e){setTimeout(e,0)}})();return function(d){c.push({f:d,a:[].slice.apply(arguments).splice(1),m:this});if(c.length==1){a(b)}}})();

Is 4ms Really Worth Worrying About?

It is.

Consider this being part of a Promise module or event handler being called hundreds or thousands of times per second. That 4ms delay will effectively limit your calls to 250 per second.

Here is a jsfiddle with my soon() function vs. the 0ms setTimeout, calling itself in a loop 1000 times. The results on my dev machine running Safari:

Testing with setTimeout(fn,0)
Total time: 5013
Testing with soon()
Total time: 2

The setTimeout version is 2500 times slower!

Need something a bit more visual?

Here is a modified Microsoft demo of a Quick Sort routine which uses setTimeout between each callback (first modified for the YuzuJS demo and then by me for this blog article). It compares 15ms timers to 4ms timers - and then to the YuzuJS setImmediate shim and finally to this soon().

Now keep in mind, that this isn't a "fair" comparison - as the YuzuJS and other approaches allow an event loop (and screen refresh) between each iteration of the loop - which allows one to visualize the sort - clearly the intention of this type of demo. The soon() approach, by contrast, does not.

However, for animation loops it is often best to use the requestAnimationFrame API rather than yielding the event thread within a work loop.

When to Use soon(). When not to.

As the last demo illustrates, soon() does not allow events and screen repaints to occur before executing the specified function - this enables extremely fast script yielding, but dictates that one must be careful not to "starve" the event loop. If you continuously call soon() within a loop for more than a few dozen milliseconds, your interface will feel unresponsive.

Soon() is best used when you wish to retain full speed of code execution, but want to alter the execution "flow" by yielding the thread such that your function is executed upon completion of the current task.

The most common case for this is when you wish to provide a function that is consistently asynchronous, yet in some cases you are able to fulfill its commitments immediately (such as obtaining a resource which is already cached). You may then use soon() to ensure the function remains asynchronous with nearly zero performance penalty.

For long-running computations that you wish to interrupt so that your interface remains responsive, soon() is not appropriate. For this purpose, either use a true setImmediate shim or setTimeout with a 0ms delay. Just be sure to recall that with setTimeout() clamping will impose the 4ms minimum delay so do not yield more than a few times per second. And do look into WebWorkers which may be an even better option.

And as said above, use requestAnimationFrame for periodic visual updates, as it is much better for animations than any other method mentioned here.

 


If you or your organization could use a somewhat fanatical developer on your current or next project, lets talk. I am passionate about the user experience, easy to work with, and I get stuff done. Lets build something great!


Speed up your Websites with a Faster setTimeout using soon() Tweet This!
Glenn is the founder of bluejava K.K., and has been providing consulting services and developing web/mobile applications for over 15 years. Glenn lives with his wife and two children in Fujisawa, Japan.
Comments
Sammy Davis
March 11, 2017
Nice article and nice function! Your adapted demo does not work somehow: http://www.bluejava.com/int/yield/ (it looks like the graph library is missing as it looks rather distorted...), the other versions of the setImmediate and the microsoft one do work...
Add Comment
Displayed alongside comment
Optional. Not displayed, but used for avatar
Optional
Don't enter anything here: