That is how JavaScript Event Loop works.
Calling setTimeout
... doesn't execute the callback function after the given interval.
The execution depends on the number of waiting tasks in the queue.
... the delay is the minimum time required for the runtime to process the request (not a guaranteed time).
https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop#zero_delays
Instead of setTimeout()
you can use window.requestAnimationFrame()
and calculate elapsed time for delay by yourself.
Window.requestAnimationFrame() - Web APIs | MDN
The window.requestAnimationFrame()
method tells the browser that you wish to perform an animation and requests that the browser calls a specified function to update an animation before the next repaint. The method takes a callback as an argument to be invoked before the repaint.
... will request that your animation function be called before the browser performs the next repaint. The number of callbacks is usually 60 times per second, but will generally match the display refresh rate in most web browsers
https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame
performance.now() - Web APIs | MDN
https://developer.mozilla.org/en-US/docs/Web/API/Performance/now
In our situation, we don't want to do any animation but want to just use it for a better-precision timeout.
const delayMs = 1000;
const startTime = performance.now();
function delay(func) {
const delayStartTime = performance.now();
function delayStep() {
// Run again if still elapsed time is less than a delay
if (performance.now() - delayStartTime <= delayMs) {
window.requestAnimationFrame(delayStep);
}
else
{
// Run the delayed function
func();
}
}
// Run first time
window.requestAnimationFrame(delayStep);
}
// Trying `setTimeout()`
setTimeout(() => doSomeJob('setTimeout()'), delayMs);
// Trying `delay()`
delay(() => doSomeJob('delay()'));
// Function that we'd like to run with a delay
function doSomeJob(marker)
{
const elapsedTime = performance.now() - startTime;
console.log(`${marker}: Ran after ${elapsedTime / 1000} seconds`);
}
If you run it many times, you'll see that delay()
is pretty much all the time better than setTimeout()
. The difference is very small because there is nothing else happens on the page. If there will be something intensive running, setTimeout()
should demonstrate worse "precision".