Recursive setTimeout vs setInterval

JavaScript/TypeScript

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.

Sponsored links

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 to a single resource that the process might update it
  • 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.

BlogPost/src/simple-code/recursive-function at master · yuto-yuto/BlogPost
Contribute to yuto-yuto/BlogPost development by creating an account on GitHub.

Comments

Copied title and URL