← Back to context

Comment by jagged-chisel

7 hours ago

Have I not always heard that timeout-based callbacks always run at or after the timeout, but never before?

“Do this {} at least Xms from now”, right?

Sure, but the nuance here is there is a (otherwise usable) range of values for which the timers are only ever "after" instead of "at or after". I.e. the lower bound is artificially increased while the upper bound remains unlimited.

  • I don’t think “artificially increased” is correct. See your sibling. If the runtime waits until expiry, and only then adds the task to the end of the work queue, there’s no point at which any delayed work could happen at expiry except the work to place it on the end of (an empty) queue.

    Any busy runtime (e.g. one with lots of parallel tasks, plus anything running less than optimally) will have a delay.

    • Artificially increased is what's happening when you request a timeout of 0 and the browser always makes it 4 ms or more.

      Imagine this code:

          let value = 0;
      
          (function tick () {
            value += 1;
            console.log(value);
            setTimeout(tick, 1);
          })();
      

      If you let `tick()` run for 1 second, what would you expect the `value` to be? Theoretically, it should be around 1,000, because all you're doing is running a function that increments the `value` and then puts itself back onto the execution queue after a 1 ms delay. But because of the 4 ms delay that browsers implement, you'll never see `value` go above 250, because the delay is being artificially increased.

this has always been my understanding as well - it schedules a function to run at time, but it won't pre-empt something that is blocking when it needs to run so there's always the possibility that it has to wait for another function to finish before it can run - a timeout is not a guarantee.

Yeah, exactly. Timeout based callbacks register a timer with the runtime, and when the timer is up, then the callback gets added to the end of the task queue (so once the timeout is up, you've got to wait for the current loop iteration to finish executing before your callback gets executed).

I always kinda figured that any "timer" in any language would technically need to work that way unless you're running a very fancy real-time system because multitasking, especially in high load scenarios, means there just may not be clock cycles available for your task at the exact millisecond you set something to execute at.

So it is with JS; I kinda figured EVERYTHING would need to be heavily throttled in a browser in order to respect the device running that browser.