Методы замедления (Throttling) служат для контроля того, сколько раз мы разрешаем выполнение функции за определенный период времени. Обычно throttling реализуется через Higher Order Function. Функция — обертка должна контролировать, чтобы callback функция вызывалась не чаще одного раза каждые X миллисекунд. Callback функция вызывается немедленно и не может быть вызвана снова в течение оставшегося времени ожидания.
Задача на реализацию Throttling часто дается на интервью и на первый взгляд кажется тривиальной, но и тут есть свои нюансы.
Давайте реализуем функцию throttle, которая принимает функцию callback и время ожидания. Вызов throttle() должен возвращать новую функцию, которая будет вызывать внутри себя callback функцию в соответствии с описанным выше поведением.
Примеры использования
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
Решение
Существует два основных способа решения данной задачи — с использованием setTimeout или с использованием Date.now()
Решение через 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(); } } }
Рассмотрим решение через Date.now(). Решение довольно тривиальное — мы запоминаем последнее время вызова, и используем его для определения, нужно ли вызывать функцию func, или нет. На мой взгляд — данное решение предпочтительнее, поскольку оно не добавляет новый вызов в Event Loop.
Решение через 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); } } }
Тут тоже все довольно тривиально — мы используем setTimeout для выставления актуального значения флага shouldThrottle. На основании текущего значения флага, мы определяем, нужно или нет вызывать функцию func.
На что стоит обратить внимание?
Будьте внимательны с тем, чтобы функция корректно работала с параметром this.
Вызов исходной функции func должен сохранять исходный указатель this. Поэтому:
-
Arrow-функции нельзя использовать для объявления внутренней функции.
-
Вызов исходной функции через func(…args) также нельзя использовать.
Для вызова исходной функции, предпочтительнее использовать func.apply или func.call, в зависимости от Ваших предпочтений (с появлением spread оператора они по большей части эквивалентны).
В следующей статье, я хочу разобрать решение задачи с Leetcode 2636, Promise Pool. Похожую (немного усложненную) задачу давали в Yandex на Two Days Offer в этом году.
ссылка на оригинал статьи https://habr.com/ru/articles/821673/
Добавить комментарий