Решение задач Front End с интервью. Throttle

от автора

Методы замедления (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/