What’s the difference between recursive setTimeout
and setInterval
? How can we choose one of them properly? What’s the recursive function? How can we implement it? This post answers those questions.
Go to chapter 6 if you just want template recursive setTimeout
function.
Basic recursive function
Let’s consider when to use a recursive function. A recursive function is useful when we want to implement the factorial function. The factorial function is for example
5! = 5 * 4 * 3 * 2 * 1
If we use a while loop its code looks like the following.
function calcFactorialByWhile(value: number): void {
if (value < 0) {
throw new Error("Value must be bigger than 0.");
}
let result = 1;
while (value !== 0) {
result *= value;
value--;
}
console.log(result);
}
calcFactorialByWhile(5);
// 120
By recursive function
function calcFactorialOf(value: number): number {
if (value < 0) {
throw new Error("Value must be bigger than 0.");
}
if (value === 0) {
return 1;
}
return value * calcFactorialOf(value - 1);
}
const result = calcFactorialOf(5);
console.log(result);
// 120
We need a condition to stop the recursion. The recursive function is called with different parameters until it fulfills the condition to stop. In this case, the start number is decremented and eventually becomes 0.
Recursive setTimeout
First, I prepared the sleep function to imitate a slow process. It returns Promise
and it is resolved when the callback for setTimeout is triggered.
function sleep(ms: number): Promise<void> {
return new Promise((resolve) => {
global.setTimeout(() => resolve(), ms);
});
}
Check this post if you don’t know Promise well.
Let’s define the recursive setTimeout function. It calculates the interval time to call the function. The main statements for this are sleep and setTimeout.
let lastTime: number | undefined;
let timeoutCound = 0;
async function recursiveSetTimeout(
processMs: number,
timeoutMs: number
): Promise<void> {
await sleep(processMs);
showIntervalTime();
global.setTimeout(() => {
recursiveSetTimeout(processMs, timeoutMs); // recursive call
}, timeoutMs);
function showIntervalTime() {
const now = Date.now();
if (lastTime) {
console.log(`setTimeout(${++timeoutCound}): ${now - lastTime}`);
}
lastTime = now;
}
}
const processMs = 3000;
const intervalMs = 1000;
recursiveSetTimeout(processMs, intervalMs);
// setTimeout(1): 4020
// setTimeout(2): 4015
// setTimeout(3): 4008
// setTimeout(4): 4010
// setTimeout(5): 4015
Interval is about 4 seconds even though the interval is set to 1 second because the timeout timer doesn’t start until setTimeout is called in the function. In other words, its timer stops once it calls the callback. The program doesn’t go forward as long as the internal function is processing. Sleep function is async but its caller waits by await
keyword until its job is done. Therefore, it takes additional seconds to trigger the next setTimeout
callback.
Comparison between setTimeout and setInterval
setInterval
function is similar to recursive setTimeout
but it’s different. Let’s see the difference in the same example.
const processMs = 3000;
const intervalMs = 1000;
let lastTime2: number | undefined;
let intervalCount = 0;
global.setInterval(async () => {
await sleep(processMs)
const now = Date.now();
if (lastTime2) {
console.log(`setInterval(${++intervalCount}): ${now - lastTime2}`);
}
lastTime2 = now;
}, intervalMs);
// setInterval(1): 1016
// setInterval(2): 1016
// setInterval(3): 1007
// setInterval(4): 994
// setInterval(5): 1003
Its interval is about 1 second as it’s specified because its timer starts once setInterval
is called and its timer keeps running. The callback function is triggered every second.
The following diagram shows the difference between setTimeout
and setInterval
. Choose the proper one depending on your specification.
How choose a proper function
Implement recursive setTimeout
in the following cases
- if the callback process needs to access a single resource that the process might update
- if the callback process finishes within the specified interval
See the following diagram.
In case using setInterval
, multiple callback processes can access a single resource. If each of them updates the resource each result can be incorrect. In addition to that, it may eat up memory usage because multiple callback processes are running. Keep an eye on the resource usage when you use setInterval
function.
Extra trial – recursive setImmediate
I tried to run the same callback by setImmediate
. It seems to be able to substitute it for while loop or recursive function. In the case of using a recursive function, it might gradually eat up memory because it doesn’t return the result.
let lastTime: number | undefined;
let count = 0;
async function recursiveImmediate(processMs: number): Promise<void> {
await sleep(processMs);
const now = Date.now();
if (lastTime) {
console.log(`setImmediate(${++count}): ${now - lastTime}`);
}
lastTime = now;
global.setImmediate(() => recursiveImmediate(processMs));
}
recursiveImmediate(3000);
// setImmediate(1): 3011
// setImmediate(2): 3020
// setImmediate(3): 3002
// setImmediate(4): 3011
// setImmediate(5): 3011
Template for recursive setTimeout
function recursiveFunc(): void {
// Write your process here
global.setTimeout(() => {
recursiveFunc()
}, intervalMs);
}
You can also use action-timer module that I uploaded to npm. You can set your own action via setter and action-timer calls the action repeatedly according to the specified interval. It is a recursive setTimeout call. In addition to that, you can interrupt it when it’s necessary. Go to the link if you want to see the example code.
You can clone the source code from my repository. It includes all source code used in this blog.
Comments