Does anyone know what can cause significant delays in the function passed to setTimeout(func, 0) being invoked? I'm seeing delays of over 1.2 seconds between when setTimeout(func, 0) is executed and when func is actually invoked.
I only see this behavior with Chrome v52 (v51 did not have it). So, it might be a Chrome issue but before I report it as a bug, I want to investigate it.
I unfortunately don't have a small case that repros it. It occurs in an AngularJS app in response to a scroll event. We handle a scroll event trigger by the mouse wheel, and as part of that, invoke setTimeout(func, 0) to do some follow up work.
Using the Chrome devtools, I can see that there is very little javascript run during this 1.2 second gap. I am not blocking the thread. In fact, I used console.profile and console.profileEnd to profile that specific 1.2 second grap. It shows it is idle 99% of that time.
Looking at the timeline in the devtools, the only thing I do see firing during that time period is some handling of "Mouse Wheel" events. There are a many of them (one every 5ish ms for about the first 1 second of the gap). Which is a little odd in itself because the mouse wheel isn't being scrolled during that time. I do one flick of the Mac Magic Mouse 's virtual scroll wheel and we bang into the top of the scrollable area. Event after we bang into the top, we keep getting the Mouse Wheel events.
Does anyone have any ideas? Could a small stream of mouse wheel events that get executed very quickly delay the callback's invocation? Is there any way to investigate why the timeout callback was invoked 1.2 seconds after its schedule time?
Edit: Added image of timeline depicting show callback of setTimeout.

setTimeout(..., 0) puts an event in the event queue. But it does not get priority over other events which were already put in the queue. Certainly when you handle scroll events, you will get into situations where many scroll events are being put in the queue, while the scroll event handler code is still running that is handling a previous scroll event.
So, in fact you'll easily get into delays when handling scroll events. When your call stack is empty, i.e. when the currently running event handler finishes, the next event will be taken from the event queue. And even though there is a timeout-related event in that queue somewhere, it will not get a higher priority: all events have to wait their turn.
For example, if there are already 10 scroll events in the queue, they will all have to be processed, and when the mouse is still scrolling, the the problem is only to get worse, as it can add more of those to the queue, and at a higher rate that your code can process them.
One way to resolve this, is to make your scroll event handler as light as possible, and do the heavy stuff asynchronously. If the handler detects that in a previous run something was already scheduled to run in that way, it could simply cancel that scheduled task, and replace it with a new version.
Sample code:
var task = -1;
window.addEventListener('scroll', function(e) {
clearTimeout(task);
task = setTimeout(function () {
// all the (heavy) processing related to scrolling comes here.
}, 0);
};
This setTimeout is not to be confused with the one from the question. But notice that when you have launched another setTimeout, it will be processed in order. The above code will just move the actual processing to the end of the queue. But if there are many more scroll events, they will each remove that timeout event from the queue, and put a new one at the end of the queue.
This will have as effect that you'll actually have fewer executions of the heavy part of the scroll handler code, which might have some negative effects on fluidity of some animation. But it will prevent lagging, and as a bonus, your other setTimeout event will get its turn sooner.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With