Solving Front End problems with interviews. Throttle
Slowdown methods (Throttling) serve to control how many times we allow a function to execute in a given period of time. Usually throttling implemented through Higher Order Function. The function – the wrapper must control that callback The function was called no more than once every X milliseconds. Callback the function is called immediately and cannot be called again for the remaining timeout.
The task of implementing Throttling is often given during interviews and at first glance seems trivial, but there are some nuances here too.
Let's implement a throttle function that takes a function callback And waiting time. Call throttle() should return a new function that will call the callback function within itself in accordance with the behavior described above.
Examples of using
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
Solution
There are two main ways to solve this problem – using setTimeout or using Date.now()
Solution via Date.now()
/**
* @callback func
* @param {number} wait
* @return {Function}
*/
export default function throttle(func, wait) {
let lastCallTime = null;
return function (...args: any[]) {
const now = Date.now();
const passed = now - lastCallTime;
if(passed > wait){
func.apply(this, args);
lastCallTime = Date.now();
}
}
}
Let's consider the solution through Date.now(). The solution is quite trivial – we remember the last call time, and use it to determine whether the function should be called func, or not. In my opinion, this solution is preferable because it does not add a new challenge to Event Loop.
Solution via setTimeout()
/**
* @callback func
* @param {number} wait
* @return {Function}
*/
export default function throttle(func, wait) {
let shouldThrottle = false
return function (...args: any[]) {
if(!shouldThrottle){
func.apply(this, args);
shouldThrottle = true;
setTimeout(() => {
shouldThrottle = false;
}, wait);
}
}
}
Here, too, everything is quite trivial – we use setTimeout to set the current flag value should Throttle. Based on the current value of the flag, we determine whether or not to call the function func.
What you should pay attention to?
Be careful to ensure that the function works correctly with the parameter this.
Calling the original function func must preserve the original pointer this. That's why:
Arrow functions cannot be used to declare an internal function.
Calling the original function via func(…args) also cannot be used.
To call the original function, it is preferable to use func.apply or func.calldepending on your preferences (with the advent of spread operator they are mostly equivalent).
In the next article, I want to analyze the solution to the problem with Leetcode 2636,Promise Pool. A similar (slightly more complicated) task was given in Yandex at Two Days Offer this year.