Arman Lalani
Arman Lalani

Reputation: 21

Can clearTimeout prevent the execution of a callback which has already been moved to the callback queue?

Consider a scenario where I have two tasks -

  1. Task 1 is a callback which is set to be executed after 1 sec using setTimeout.
  2. Task 2 is a synchronous/blocking task which takes comparatively more time than task 1. Till the time task 2 completes, task 1 would have been already pushed to the callback queue. If I call clearTimeout() after task 2, will task 1 still execute?

I have tried to simulate the above as follows -

/* module example.js */
console.log("Before");

// Task 1
const timerId = setTimeout(() => {
    console.log("Timeout executed");
}, 0);

// Task 2
// for (let i = 0; i < 4000000000; i++) {} // Before(0s), After(after some delay because of for loop)
// OR
await delay(5000); // Before(0s), Timeout Executed(0s), After(5s)

clearTimeout(timerId);

console.log("After");

async function delay(ms) {
    return new Promise((resolve, reject) => {
    setTimeout(resolve, ms);
    });
}

I have tried to simulate the task 2(a long blocking code) in two ways. The first one is a for loop which goes from i = 0 to 4000000000 and the second one is an promise based delay function. In case of for loop, Before and After is printed immediately and Timeout executed is not printed. My inference is that the clearTimeout is able to remove the task1 from the callback queue. On the other hand in the second implementation, Before and Timeout Executed is printed immediately and After is printed after 5 seconds. The output of both the examples contradicts each other.

The answer here by @Pointy(Can clearTimeout remove an unprocessed callback of a fired timeout event in JavaScript?) says that the clearTimeout should be able to remove the callback from the queue but my second implementation contradicts that and also I did not find the same on any other resources on the web.

  1. Does both the implementations correctly simualtes my scenario?
  2. Why is the behaviour different in both the scenarios and eventually can clearTimeout prevent the execution of a callback which has already been moved to the callback queue?

Upvotes: -1

Views: 81

Answers (1)

trincot
trincot

Reputation: 351039

Let me take those two questions:

  1. Do both the implementations correctly simulate my scenario?

No. The scenario you wanted to implement was "Task 2 is a synchronous/blocking task which takes comparatively more time than task 1".

But await delay(5000) is not blocking. On the contrary, await suspends the function it is part of (or if it is a top-level await, it suspends the evaluation of the module that it is part of). When the call stack has run to completion (which is immediate for your example code), we can say the synchronous execution flow has ended; the JavaScript engine can now process the job queues. So when the timer expires and the callback enters the callback queue, the JS engine is already actively monitoring its queues and will take the callback from that queue and execute it. After that, about 4 more seconds pass where the job queues are monitored again, until the awaited delay(5000) promise fulfills, which adds a job to the microtask queue. That job will be pulled from its queue and executed, which involves resuming the code below the await.

So you see that this await delay(5000) is not blocking and does not produce the scenario you intended.

  1. Why is the behaviour different in both the scenarios and clearTimeout can eventually prevent the execution of a callback which has already been moved to the callback queue?

In the first version (with the busy for loop), the JS engine does not monitor its job queues while it is fully occupied with executing the loop. Once that for loop completes, clearTimeout executes, and all the rest of the synchronous flow. Then the callstack is empty again, and the queues are monitored. But as clearTimeout had executed, there is no callback in the callback queue.

In the second version (with the await), the JS engine monitors the queues, and once the timer expires, the callback is found and pulled from the callback queue, and is executed. At this point clearTimeout has not executed yet, and it will not be executed until the delay promise has resolved. So by the time it executes, it references a timeout ID that no longer represents an active timer -- it accomplishes nothing.

Upvotes: 0

Related Questions