SetTimeout implementation
What is setTimeout()
The setTimeout() method sets a timer which executes a function or specified piece of code once the timer expires.
Calling to setTimeout function returns a timeoutID, which is a positive integer value which identifies the timer created by the call to setTimeout(). This value can be passed to clearTimeout() to cancel the timeout.
setTimeout() is an asynchronous function, meaning that the timer function will not pause execution of other functions in the functions stack.
example -
setTimeout(code)
setTimeout(code, delay)
const timerId = setTimeout(code, delay);Understanding the parameters -
- code - A function to be executed after the timer expires.
- delay - The time, in milliseconds that the timer should wait before the specified function or code is executed. If this parameter is omitted, a value of 0 is used, meaning execute "immediately", or more accurately, the next event cycle.
Implementing our setTimeout() and clearTimeout() function -
- From above specs we understand that our code will have to wait at-least for delay time to elapse before getting executed.
- It will get executed once delay time is elapsed after registering the timer and when main thread is available.
So the question comes in mind, how would we identify that our main thread is available to execute some function?
Answer is window.requestIdleCallback() function, which is provided by browser.
requestIdleCallback method queues a function to be called during a browser's idle periods.
requestIdleCallback will schedule work when there is free time at the end of a frame, or when the user is inactive. This means that there’s an opportunity to do your work without getting in the user’s way.
Info
You can read more about requestIdleCallback() on mdn docs here
implementation - for implementation purposes let's ignore the rest of the parameters passed to setTimeout function other than callback function and delay time.
(function(){
const timers = {};
function check() {
if (Object.keys(timers).length <= 0) {
return;
}
for(let [id, timeout] of Object.entries(timers)) {
const { expiry, callback } = timeout;
const now = Date.now();
if (now >= expiry) {
callback();
myClearTimeout(id);
}
}
requestIdleCallback(check);
}
function createNewId() {
let id = Math.floor(Math.random() * Math.MAX_SAFE_INTEGER);
while(timers.hasOwnProperty(id)) {
id = Math.floor(Math.random() * Math.MAX_SAFE_INTEGER);
};
return id;
}
window.mySetTimeout = function(callback, delay) {
if(typeof(callBackFn) !== 'function') throw new Error('callback should be function');
if(typeof(delay) !== 'number' || delay < 0) throw new Error('delay should be a poistive number');
const id = createNewId();
timers[id] = {
callback,
expiry: Date.now() + delay,
}
// start checking if this is the first timer
if (Object.keys(timers).length === 1) {
requestIdleCallback(check);
}
return id;
}
window.myClearTimeout = function(id) {
if(Object.hasOwn(timers, id)) {
delete timers[id];
}
}
})()- We are injecting our mySetTimeout and myClearTimeout into window object so that we can call them directly from anywhere in our code.
Note
We wrapped our implementation in an IIFE so that this piece of code does not mess with the other code and also stays abstracted from the outer world.
Usage example of our promiseRace function
const id = mySetTimeout(() => {
console.log("print after 2s")
myClearTimeout(id);
}, 2000);
constReferences
- You can read more about setTimeout on mdn docs here
- requestTimeout
- Abhishek's Implementation