Posts Tagged ‘setTimeout’

setInterval with 0ms delays within Web Workers

The 4ms minimum

Due to browser restrictions, you typically can’t have a setInterval call where the delay is set to 0, from MDN:

In modern browsers, setTimeout()/setInterval() calls are throttled to a minimum of once every 4 ms when successive calls are triggered due to callback nesting (where the nesting level is at least a certain depth), or after certain number of successive intervals.

I confirmed this by testing with the following code in Chrome and Firefox windows:

setInterval(() => { console.log(`now is ${Date.now()}`); }, 0);

In Firefox 72:

Firefox window setInterval with 0ms delay

In Chrome 79:

Chrome window setInterval with 0ms delay

The delay isn’t exact, but you can see that it typically comes out to around 4ms, as expected. However, things are a little different with web workers.

The delay with web workers

To see the timing behavior within a web worker, I used the following code for the worker:

const printNow = function() { console.log(`now is ${Date.now()}`); }; setInterval(printNow, 0); onmessage = function(_req) { };

… and created it via const worker = new Worker('worker.js');.

In Chrome, there’s no surprises, the behavior in the worker was similar to what it was in the window:

Chrome window setInterval with 0ms delay

Things get interesting in Firefox:

Firefox window setInterval with 0ms delay

Firefox starts grouping the log messages (the blue bubbles), as we get multiple calls to the function within the same millisecond. Firefox’s UI becomes unresponsive (which is weird and I didn’t expect as this is on an i7-4790K with 8 logical processors and there’s little interaction between the worker and the parent window), and there’s a very noticeable spike in CPU usage.

Takeaway

setInterval() needs a delay and you shouldn’t depend on the browser to set something reasonable. It would be nice if setInterval(.., 0) would tell the browser to execute as fast as reasonably possible, adjusting for UI responsiveness, power consumption, etc. but that’s clearly not happening here and as such it’s dangerous to have a call like this which may render the user’s browser unresponsive.

Triggering reflow for CSS3 transitions

There’s a lot written on minimizing reflow of the DOM, but one interesting situation is actually trying to force reflow in order to apply a CSS transition rule when the element is pushed into the DOM render tree (e.g. display:none to display:block, display:inline, etc.).

Problem

.test-block { width:50px; height:50px; background:#000; margin:10px; opacity:1; transition:opacity 0.5s; }
.test-block.invisible { opacity:0; }
.test-block.hide { display:none; }
<div class="ia-fadein-block test-block invisible hide"></div> $('.ia-fadein-block').removeClass('hide');
$(
'.ia-fadein-block').removeClass('invisible');

If we remove the hide and invisible classes from the div (as shown above), you’d assume that the div would transition smoothly to opacity:1 in 0.5s; however, this is not the case, the div will go to opacity:1 instantly.

What’s happening is that the removal of both classes occur prior to the browser adding the div to the DOM render tree; when the div gets rendered, it’s as if it never had the hide nor invisible class.

Reflow needs to be triggered before the invisible class is removed in order for the transition to work as expected.

Deprecated solution: setTimeout(func,0)

A setTimeout() with a delay of 0 was my go-to solution for the longest while. Here, the function argument specified would handle whatever needed to occur after the element was added to the DOM render tree (for the example presented above, this would mean the invisible class being removed in the function specified). The thinking behind using setTimeout() was that reflow would occur at some point in the current execution context and before the execution of the setTimeout() callback. This seemed true for a while, but with one of the Firefox releases last year (I don’t know specifically which) I noticed things breaking, with the Javascript engine executing the callback before reflow occured.

I did some quick hacks to fix existing code by increasing the time span before the timeout was triggered (typically 50ms-100ms), but this made a nasty hack even nastier and I wanted a more reliable solution.

Current solution: Trigger reflow manually

There are a number of calls that will trigger reflow on the DOM, but the most reliable calls, that always force reflow, seem to be the ones that return any sort of measurement that must be calculated (e.g. offsetWidth) on an element. With this in mind, the Javascript code can be modified as follows:

$('.ia-fadein-block').removeClass('hide');

// force reflow
$('.ia-fadein-block')[0].offsetWidth;
//

$('.ia-fadein-block').removeClass('invisible');

The code is for demonstration purposes; for repeated use, the hack to force reflow should really be encapsulated in a function, as there is no guarantee that the reflow behavior triggered by querying offsetWidth will remain consistent.

Ideal solution: API Method to trigger reflow or delay certain style changes post-reflow

The solution presented above is still a hack and may very well break in the future, as there is no guarantee that browsers won’t cache properties like offsetWidth in further attempts to minimize reflow. A robust, future-proof, solution is needed and it’s amazing one hasn’t come to fruition as yet.

A Mozilla bug report documents this issue and David Baron presents some workable solutions.