If you're writing some code on a time-delayed-loop in JavaScript, there are 2 main options:
However, each has a potential downside:
setInterval
schedules the code to run at the given interval, regardless of whether the last invocation has completed. This means that, if the function takes longer than the delay to execute, it'll schedule the next invocation before the current invocation is finished, which can lead to an avalanche of calls being piled on without bound if the functions continue to take equivalent amounts of time to run and aren't being delegated to truly concurrent threads.- Recursive
setTimeout
is a memory leak when run for unbounded time on Firefox and Chrome (though, interestingly: not in Safari et. al.!).
If you want "the best of both worlds": code that waits for invocation n to finish before starting invocation n+1, but does not leak memory on Firefox and Chrome, you'll have to take advantage of await
to logically emulate the tail-recursion iteratively:
var [setRepeatedTimeout, clearRepeatedTimeout] = (() => {
const asleep = (delay => new Promise(resolve => setTimeout(resolve, delay)));
const repeatedTimeoutIntervals = [];
function setRepeatedTimeout(f, delay, ...arguments) {
//Like setInterval, but waits for an invocation to complete before scheduling the next one
//(Bonus: supports both classic and async functions)
const intervalID = repeatedTimeoutIntervals.push(delay) - 1;
(async () => {
await asleep(delay);
while(intervalID in repeatedTimeoutIntervals) {
await f(...arguments);
await asleep(delay);
}
})();
return intervalID;
}
function clearRepeatedTimeout(intervalID) {
//Clears loops set by setInterval()
delete repeatedTimeoutIntervals[intervalID];
}
return [setRepeatedTimeout, clearRepeatedTimeout];
})();