Timer - JavaScript Challenges
Mitchell
Posted on November 4, 2024
You can find all the code in this post at the repo Github.
Async programming timer related challenges
Cache with time limit
class TimeLimitedCache {
constructor() {
this._cache = new Map();
}
set(key, value, duration) {
const found = this._cache.has(key);
if (found) {
clearTimeout(this._cache.get(key).ref);
}
this._cache.set(key, {
value,
ref: setTimeout(() => {
this._cache.delete(key);
}, duration),
});
return found;
}
get(key) {
if (this._cache.has(key)) {
return this._cache.get(key);
} else {
return -1;
}
}
count() {
return this._cache.size;
}
}
// Usage example
const timeLimitedCache = new TimeLimitedCache();
console.log(timeLimitedCache.set(1, 'first', 1000)); // => false
console.log(timeLimitedCache.get(1).value); // => 'first'
console.log(timeLimitedCache.count()); // => 1
setTimeout(() => {
console.log(timeLimitedCache.count()); // => 0
console.log(timeLimitedCache.get(1)); // => -1
}, 2000);
Cancel interval
/**
* @param {Function} callback
* @param {number} delay
* @param {...any} args
* @returns {Function}
*/
function setCancellableInterval(callbackFn, delay, ...args) {
const timerId = setInterval(callbackFn, delay, ...args);
return () => {
clearInterval(timerId);
};
}
// Usage example
let i = 0;
// t = 0:
const cancel = setCancellableInterval(() => {
i++;
}, 100);
// t = 50:
cancel();
// t = 100: i is still 0 because cancel() was called.
Cancel timeout
/**
* @param {Function} callbackFn
* @param {number} delay
* @param {...any} args
* @returns {Function}
*/
function setCancellableTimeout(callbackFn, delay, ...args) {
const timerId = setTimeout(callbackFn, delay, ...args);
return () => {
clearTimeout(timerId);
};
}
// Usage example
let i = 0;
// t = 0:
const cancel = setCancellableTimeout(() => {
i++;
}, 100);
// t = 50:
cancel();
// t = 100: i is still 0 because cancel() was called.
Clear all timeout timers
/**
* cancel all timer from window.setTimeout
*/
const timerQueue = [];
const originalSetTimeout = window.setTimeout;
window.setTimeout = function (callbackFn, delay, ...args) {
const timerId = originalSetTimeout(callbackFn, delay, ...args);
timerQueue.push(timerId);
return timerId;
}
function clearAllTimeout() {
while (timerQueue.length) {
clearTimeout(timerQueue.pop());
}
}
// Usage example
setTimeout(func1, 10000)
setTimeout(func2, 10000)
setTimeout(func3, 10000)
// all 3 functions are scheduled 10 seconds later
clearAllTimeout()
// all scheduled tasks are cancelled.
Debounce
/**
* @param {Function} fn
* @param {number} wait
* @return {Function}
*/
function debounce(fn, wait = 0) {
let timerId = null;
return function (...args) {
const context = this;
clearTimeout(timerId);
timerId = setTimeout(() => {
timerId = null;
fn.call(context, ...args);
}, wait);
}
}
// Usage example
let i = 0;
function increment() {
i += 1;
}
const debouncedIncrement = debounce(increment, 100);
// t = 0: Call debouncedIncrement().
debouncedIncrement(); // i = 0
// t = 50: i is still 0 because 100ms have not passed.
// Call debouncedIncrement() again.
debouncedIncrement(); // i = 0
// t = 100: i is still 0 because it has only
// been 50ms since the last debouncedIncrement() at t = 50.
// t = 150: Because 100ms have passed since
// the last debouncedIncrement() at t = 50,
// increment was invoked and i is now 1 .
//-----------------------------------------
// debounce with leading & trailing `options`
function debounce(func, wait, option = { leading: false, trailing: true }) {
let timerId = null;
let leadingCallArgs = null;
return function (...args) {
if (!timerId && option.leading) {
leadingCallArgs = args;
func.apply(this, args);
}
clearTimeout(timerId);
timerId = setTimeout(() => {
if (args !== leadingCallArgs && option.trailing) {
func.apply(this, args);
}
timerId = null;
leadingCallArgs = null;
}, wait);
};
}
//-----------------------------------------
function debounce(func, wait = 0) {
let timeoutId = null;
let context = undefined;
let argsToInvoke = undefined;
function clearTimer() {
clearTimeout(timeoutId);
timeoutId = null;
}
function invoke() {
if (timeoutId == null) {
return;
}
clearTimer();
func.call(context, ...argsToInvoke);
}
function fn(...args) {
clearTimer();
argsToInvoke = args;
context = this;
timeoutId = setTimeout(function () {
invoke();
}, wait);
}
fn.cancel = clearTimer;
fn.flush = invoke;
return fn;
}
Throttle
/**
* @param {Function} fn
* @param {number} wait
* @return {Function}
*/
function throttle(fn, wait = 0) {
let shouldThrottle = false;
return function (...args) {
if (shouldThrottle) {
return;
}
shouldThrottle = true;
setTimeout(() => {
shouldThrottle = false;
}, wait);
fn.call(this, ...args);
}
}
// Usage example
let i = 0;
function increment() {
i++;
}
const throttledIncrement = throttle(increment, 100);
// t = 0: Call throttledIncrement(). i is now 1.
throttledIncrement(); // i = 1
// t = 50: Call throttledIncrement() again.
// i is still 1 because 100ms have not passed.
throttledIncrement(); // i = 1
// t = 101: Call throttledIncrement() again. i is now 2.
// i can be incremented because it has been more than 100ms
// since the last throttledIncrement() call at t = 0.
throttledIncrement(); // i = 2
//---------------------------------------
//Support subsequent function calls during the throttle period
function throttle(fn, wait = 0) {
let shouldThrottle = false;
let savedArgs = null;
let savedThis = null;
return function (...args) {
if (shouldThrottle) {
savedArgs = args;
savedThis = this;
return;
}
fn.call(this, ...args);
shouldThrottle = true;
setTimeout(() => {
shouldThrottle = false;
if (savedArgs) {
fn.call(this, ...savedArgs);
savedArgs = null;
savedThis = null;
}
}, wait);
};
}
//---------------------------------------
// throttle with leading & trailing `options`
function throttle(func, wait, option = { leading: true, trailing: true }) {
let waiting = false;
let lastArgs = null;
return function wrapper(...args) {
if (!waiting) {
waiting = true;
const startWaitingPeriod = () =>
setTimeout(() => {
if (option.trailing && lastArgs) {
func.apply(this, lastArgs);
lastArgs = null;
startWaitingPeriod();
} else {
waiting = false;
}
}, wait);
if (option.leading) {
func.apply(this, args);
} else {
lastArgs = args;
}
startWaitingPeriod();
} else {
lastArgs = args;
}
};
}
Repeat interval
const URL = 'https://fastly.picsum.photos/id/0/5000/3333.jpg?hmac=_j6ghY5fCfSD6tvtcV74zXivkJSPIfR9B8w34XeQmvU';
function fetchData(url) {
return fetch(url)
.then((response) => {
if (!response.ok) {
throw new Error(`Error: ${response.status}`);
}
return response.blob();
})
.then((data) => {
console.log(data);
})
.catch((err) => {
console.log(`Error: ${err}`);
});
}
function repeat(callbackFn, delay, count) {
let currentCount = 0;
const timerId = setInterval(() => {
if (currentCount < count) {
callbackFn();
currentCount += 1;
} else {
clearInterval(timerId);
}
}, delay);
return {
clear: () => clearInterval(timerId),
}
}
// Usage example
const cancel = repeat(() => fetchData(URL), 2000, 5);
setTimeout(() => {
cancel.clear();
}, 11000);
Resumable interval
/**
* @param {Function} callbackFn
* @param {number} delay
* @param {...any} args
* @returns {{start: Function, pause: Function, stop: Function}}
*/
function createResumableInterval(callbackFn, delay, ...args) {
let timerId = null;
let stopped = false;
function clearTimer() {
clearInterval(timerId);
timerId = null;
}
function start() {
if (stopped || timerId) {
return;
}
callbackFn(...args);
timerId = setInterval(callbackFn, delay, ...args);
}
function pause() {
if (stopped) {
return;
}
clearTimer();
}
function stop() {
stopped = true;
clearTimer();
}
return {
start,
pause,
stop,
};
}
// Usage example
let i = 0;
// t = 0:
const interval = createResumableInterval(() => {
i++;
}, 10);
// t = 10:
interval.start(); // i is now 1.
// t = 20: callback executes and i is now 2.
// t = 25:
interval.pause();
// t = 30: i remains at 2 because interval.pause() was called.
// t = 35:
interval.start(); // i is now 3.
// t = 45: callback executes and i is now 4.
// t = 50:
interval.stop(); // i remains at 4.
Implement setInterval()
/**
* @param {Function} callbackFn
* @param {number} delay
* @return {object}
*/
// use `requestAnimationFrame`
function mySetInterval(callbackFn, delay) {
let timerId = null;
let start = Date.now();
// loop
function loop() {
const current = Date.now();
if (current - start >= delay) {
callbackFn();
start = current;
}
timerId = requestAnimationFrame(loop);
}
// run loop
loop();
return {
clear: () => cancelAnimationFrame(timerId),
}
}
const interval = mySetInterval(() => {
console.log('Interval tick');
}, 1000);
// cancel
setTimeout(() => {
interval.clear();
}, 5000);
// use `setTimeout`
function mySetInterval(callbackFn, delay) {
let timerId = null;
let start = Date.now();
let count = 0;
// loop
function loop() {
const drift = Date.now() - start - count * delay;
count += 1;
timerId = setTimeout(() => {
callbackFn();
loop();
}, delay - drift);
}
// run loop
loop();
return {
clear: () => clearTimeout(timerId),
}
}
const interval = mySetInterval(() => {
console.log('Interval tick');
}, 1000);
// cancel
setTimeout(() => {
interval.clear();
}, 5000);
Implement setTimeout()
function setTimeout(callbackFn, delay) {
let elapsedTime = 0;
const interval = 100;
const intervalId = setInterval(() => {
elapsedTime += interval;
if (elapsedTime >= delay) {
clearInterval(intervalId);
callbackFn();
}
}, interval);
}
// Usage example
mySetTimeout(() => {
console.log('This message is displayed after 2 seconds.');
}, 2000);
Reference
- Window: setTimeout() method - MDN
- Window: setInterval() method - MDN
- Window: clearInterval() method - MDN
- Window: clearTimeout() method - MDN
- 2715. Timeout Cancellation - LeetCode
- 2725. Interval Cancellation - LeetCode
- 2622. Cache With Time Limit - LeetCode
- 2627. Debounce - LeetCode
- 2676. Throttle - LeetCode
- Rate limiting - Wikipedia.org
- 28. implement clearAllTimeout() - BFE.dev
- 4. implement basic throttle() - BFE.dev
- 5. implement throttle() with leading & trailing option - BFE.dev
- 6. implement basic debounce() - BFE.dev
- 7. implement debounce() with leading & trailing option- BFE.dev
- JavaScript Asynchronous: Exercises, Practice, Solutions - w3resource
- GreatFrontEnd
💖 💪 🙅 🚩
Mitchell
Posted on November 4, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.