Recently I discovered a footgun in real life, which was related to setTimeout, I had to run a timeout for say 28 days for a sale timer, I had a UTC timestamp for the end day, so with the naive approach, I did this
const date1 = new Date(timestamp1);
// Difference in milliseconds
const timeout = date2.getTime() - Date.now();
setTimeout(()=>{
// some code to turn off some flags / remove some banner
},timeout);
To my surprise, this did not work or worked too well, since the code within setTimeout executed without waiting for the timeout, I decided to debug in the browser, and I saw that the control jumps into the setTimeout callback almost instantly.
What's the problem here ?
Looking at MDN page of setTimeout, https://developer.mozilla.org/en-US/docs/Web/API/setTimeout#maximum_delay_value , it was clear that there is a max limit until which setTimeout() will run accurately, specifically
2,147,483,647ms or (24.8 days) or (2**31 - 1) ms, this is because browsers store the delay as a 32-bit signed integer internally.
So whenever you pass in a timeout of more than 24.8 days, there is an integer overflow and the code is executed immediately or rather with a lower timeout duration than expected. That's a bummer, and there is no error !!!
Possible solutions for this problem
const days = 30;
const timeout = days * 24 * 60 * 60 * 1000;
console.log('timeto', timeout);
setTimeout(function () {
console.log('ticked immediately'); // --> executed almost instantly
}, timeout);
class LongTimeout {
constructor(cb, timeout) {
this.timeStart = document.timeline
? document.timeline.currentTime
: performance.now();
this.lastAnimationFrame = this.runTimer(cb, timeout);
}
runTimer(cb, timeout) {
if(this.cancelled) return;
const currTimeStamp = performance.now();
const elapsed = currTimeStamp - this.timeStart;
if (elapsed >= timeout) {
cb();
window.cancelAnimationFrame(this.lastAnimationFrame);
} else {
console.log('tick', elapsed, timeout);
this.lastAnimationFrame = requestAnimationFrame(() =>
this.runTimer(cb, timeout)
);
}
}
cancelTimeout() {
window.cancelAnimationFrame(this.lastAnimationFrame);
this.cancelled = true;
this.lastAnimationFrame = null;
}
}
const longTimer = new LongTimeout(() => {
console.log(`Tick after ${timeout}`); // timeout works -> does not execute immediately
}, timeout);
Top comments (0)