Reputation: 21
Consider a scenario where I have two tasks -
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.
Upvotes: -1
Views: 81
Reputation: 351039
Let me take those two questions:
- 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.
- 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