Reputation: 4352
In the following article, use-case 1
, they change positions of the setTimeout
call inside the count
function:
https://javascript.info/event-loop#use-case-1-splitting-cpu-hungry-tasks
In the second case its much faster and they explain it by the following sentence:
If you run it, it’s easy to notice that it takes significantly less time.
Why?
That’s simple: as you remember, there’s the in-browser minimal delay of 4ms for many nested setTimeout calls. Even if we set 0, it’s 4ms (or a bit more). So the earlier we schedule it – the faster it runs.
For me it's a very unclear explanation and I don't understand what they mean at all. Could anyone explain more clearly and in detail why these two cases take different time?
Upvotes: 1
Views: 693
Reputation: 137014
setTimeout(fn, t)
will schedule synchronously fn
to fire at now + t
.
The explanation in your quote is then spot on: if you schedule your task before locking the computer for some time, it will fire before than if you scheduled it after.
// now = wall clock 0;
setTimeout(fn, 1000); // schedules `fn` to fire at wall clock 0 + 1000 = 1000
lockCPUFor(5000);
// now = wall clock 5000;
setTimeout(fn, 1000); // schedules `fn` to fire at wall clock 5000 + 1000 = 6000;
at the end of the task we are at now = 5000 + a few ms. The first timeout is passed by 4s so we'll execute it immediately. However the second timeout is scheduled to fire in about a second from now, so we'll wait about a second.
const origin = performance.now();
function fn(name) {
console.log(name, performance.now() - origin);
}
fn("begin"); // ~0.05
setTimeout(() => fn("setTimeout before"), 1000); // ~ 5010
lockCPUFor(5000);
setTimeout(() => fn("setTimeout after"), 1000); // ~6000
function lockCPUFor(t) {
const now = performance.now();
while (performance.now() - now < t) {}
}
But in our case the delay is 0 and not 1000, so it should not matter.
As your quote states, passing 0
doesn't mean that you will really have a zero timeout. Some environments (e.g Chrome & node.js) will always have a 1ms minimum timeout (though Chrome is actively trying to remove that minimum timeout), Firefox will also add some minimum timeout, "at page load", (actually it's more that they have a special task-queue for timeouts at page-load which does have lesser priority than any other task queue).
And every UA following the HTML specs will have a 4ms timeout at the 5th level of nesting:
let startTime;
let i = 0;
const fn = () => {
console.log("iteration #%s took %sms", i + 1, performance.now() - startTime);
if (++i < 6) {
startTime = performance.now();
setTimeout(fn, 0);
}
}
const startTest = () => {
startTime = performance.now();
setTimeout(fn, 0)
}
// avoid Firefox's weird at-load task queue
onload = startTest;
So even when you pass 0
, these few ms that are added automatically by the UA will have an impact on when the callback has been scheduled, and scheduling before the long task will indeed allow to avoid most of these limits (if the long task lasts longer).
But note that if you need to hook to the event loop faster than setTimeout, there are means.
Upvotes: 2