Is there a faster way to yield to Javascript event loop than setTimeout(0)?

Yes, the message queue will have higher importance than timeouts one, and will thus fire at higher frequency.

You can bind to that queue quite easily with the MessageChannel API:

let i = 0;
let j = 0;
const channel = new MessageChannel();
channel.port1.onmessage = messageLoop;

function messageLoop() {
  i++;
  // loop
  channel.port2.postMessage("");
}
function timeoutLoop() {
  j++;
  setTimeout( timeoutLoop );
}

messageLoop();
timeoutLoop();

// just to log
requestAnimationFrame( display );
function display() {
  log.textContent = "message: " + i + '\n' +
                    "timeout: " + j;
  requestAnimationFrame( display );
}
<pre id="log"></pre>

Now, you may also want to batch several rounds of the same operation per event loop.

Here are a few reasons why this method works:

  • Per specs, setTimeout will get throttled to a minimum of 4ms after the 5th level of call, that is after the fifth iteration of OP's loop.
    Message events are not subject to this limitation.

  • Some browsers will make the task initiated by setTimeout have a lower priority, in some cases.
    Namely, Firefox does that at page loading, so that scripts calling setTimeout at this moment don't block other events ; they do even create a task queue just for that.
    Even if still un-specced, it seems that at least in Chrome, message events have a "user-visible" priority, which means some UI events could come first, but that's about it. (Tested this using the up-coming scheduler.postTask() API in Chrome)

  • Most modern browsers will throttle default timeouts when the page is not visible, and this may even apply for Workers.
    Message events are not subject to this limitation.

  • As found by OP, Chrome does set a minimum of 1ms even for the first 5 calls.


But remember that if all these limitations have been put on setTimeout, it's because scheduling that many tasks at such a rate has a cost.

Use this only in a Worker thread!

Doing this in a Window context will throttle all the normal tasks the browser has to handle, but which they'll consider less important, like Network requests, Garbage Collection etc.
Also, posting a new task means that the event loop has to run at high frequency and will never idle, which means more energy consumption.