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
Top comments (0)