1

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?

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
Nikita Vlasenko
  • 4,004
  • 7
  • 47
  • 87

1 Answers1

1

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.

Kaiido
  • 123,334
  • 13
  • 219
  • 285
  • But in our case the delay is `0` and not `1000`, so it should not matter. In the top image, scheduling a clock in `1000` or `6000` should not matter because even if the first timeout is scheduled in `1000` it will not be run because a `5000` task is on the stack, so it needs to wait for it to finish up. Thats one of the reasons I see no difference between these two timeout placements. – Nikita Vlasenko Nov 13 '21 at 15:46
  • With `0` delay it will be `0` + `5000` vs `5000` + `0` – Nikita Vlasenko Nov 13 '21 at 15:48
  • @NikitaVlasenko as your quote states, after the 4th loop of setTimeout there is a minimum 4ms delay, so it's `0+4` vs `5000+4`. – Kaiido Nov 13 '21 at 15:56
  • I don't get what you mean, sorry. 4th loop? It does not matter that its `5000` delay while the code is running because it runs on the `stack` and in both cases `setTimeout` would be scheduled to run `after` the heavy computation. So, why that would not be the case? Or what am I missing here? – Nikita Vlasenko Nov 13 '21 at 16:16
  • https://developer.mozilla.org/en-US/docs/Web/API/setTimeout#reasons_for_delays_longer_than_specified look at "Nested". So at the 5th time a loop `setTimeout(function fn(){ setTimeout(fn) })` calls `setTimeout` the delay will be 4ms and not 0. And here you experience what I show in my answer with a delay of 1s. – Kaiido Nov 14 '21 at 00:26