Javascript setTimeout(): when is callback called?

Javascript setTimeout(): when is callback called?

The tale of the timing of setTimeout callback invocation

Introduction

Hey there, My name is Karun and I am building web applications since 2012. After reading this article, you'll have better understanding of when setTimeout executes the callback passed.

What is setTimeout() function in javascript ?

Almost all javascript programmers start writing async javascript code by first interacting with this function setTimeout. Per MDN, its defined as a "method sets a timer which executes a function or specified piece of code once the timer expires".

Now usually without much thought, the interpretation of these words is pretty simple i.e. setTimeout(() => {}, 100) executes the callback passed when timer runs out 100ms. The caveat however is that the timer mentioned in function is the earliest time to execute the callback and it can be delaaaayed based on the process activity. Let me explain.

How long does it take to execute setTimeout callback?

Imagine there's main function which does some work() and a setTimeout(cb, 0). Now if work() took 500ms then callback cb is bound to be execute only after cb has returned so it will execute at ~501ms or at 601ms? [answer: "It depends 🤯" ]

Remember this about the Event loop: It pushes setTimeout into the task queue with waiting timer started. Event loop looks for the callStack to complete and invoke the timeout callback.

Run below code and observe the output.

(function run_this_in_console() {
    console.time('function run time');
    console.log("first");
    setTimeout(() => {
        console.log("second")
        console.timeEnd('timeout run time');
    }, 100);
    console.timeEnd('function run time');
    console.time('timeout run time');
    return "third"
})()

Whats happening is: It prints "first" then returns "third" which gets printed on console and later it executes the timeout function as expected and the time taken by timeout callback is almost 100ms as provided in the function. But what if the function takes time to run what does that look like ?

(function run_this_in_console() {
    console.time('function run time');
    console.time('total time');
    console.log("first");
    /* blocking code start */ 
    let i = 0;
    while(i < 500000000) {
        Math.round(i);
        i++;
    }
    /* blocking code end */
    setTimeout(() => {
        console.log("second")
        console.timeEnd('timeout run time');
        console.timeEnd('total time');
    }, 100);

    console.timeEnd('function run time');
    console.time('timeout run time');
    return "third"
})()

We added a chunk of work to the main function logic which took ~250ms (in my machine) and then took ~100ms to execute the callback. It similar to previous logic we ran. But now here's a kicker:

(function run_this_in_console() {
    console.time('function run time');
    console.time('total time');
    console.log("first");
    setTimeout(() => {
        console.log("second")
        console.timeEnd('timeout run time');
        console.timeEnd('total time');
    }, 100);
    let i = 0;
    while(i < 500000000) {
        Math.round(i);
        i++;
    }
    console.timeEnd('function run time');
    console.time('timeout run time');
    return "third"
})()

Interestingly this time this function took 100ms less than the previous total time. 🤣 This is no voodoo. It really matters where in your function you register the timeout and its time to execution gets affected.

In latest example the Event loop registered the timeout and the timer started to run. The main function had to work to do which took longer than the timeout. So effectively it was invoked immediately after this function returned. As opposed to the previous example where the timeout was registered just before the function was returned and all the processing was already finished.

Conclusion

MDN mentioned the method is called when the timer expires but the catch is when the timer is invoked in the first place and the runtime of all the code needs to be run before the callback.

I hope you are able to now tell when setTimeout will run. Would you like to learn how promises execute when added to above mix? Let me know below in the comments what you think! See you in the next post.