Оригінальна публікація: Debounce – How to Delay a Function in JavaScript (JS ES6 Example)

У JavaScript функція debounce гарантує, що ваш код запускається лише один раз за введення користувача. Пропозиції у вікні пошуку, автоматичне збереження текстових полів і усунення подвійних натискань кнопок — усе це випадки використання функції debounce.

У цьому посібнику ми дізнаємося, як створити функцію debounce в JavaScript.

Що таке debounce?

Термін debounce походить від електроніки. Коли ви натискаєте кнопку, скажімо, на пульті дистанційного керування телевізором, то сигнал надходить до мікрочіпа пульта настільки швидко, що перш ніж ви встигнете відпустити кнопку, він неодноразово повторюється і мікрочіп реєструє ваше «клацання» декілька разів.

debounce-button

Щоб змінити це, після отримання сигналу від кнопки мікрочіп припиняє обробку сигналів від неї на кілька мікросекунд — на той час, коли ви фізично не в змозі натиснути її знову.

Debounce у JavaScript

У JavaScript сценарій використання схожий. Ми хочемо запустити функцію, але лише один раз для кожного випадку використання.

Скажімо, ми хочемо показати пропозиції для пошукового запиту, але лише після того, як відвідувач завершить його введення.

Або ми хочемо зберегти зміни у формі, але лише тоді, коли користувач не працює активно над цими змінами, оскільки кожне «збереження» коштує нам подорожі до бази даних.

І мій улюблений приклад: деякі люди так звикли до Windows 95, що й досі двічі клацають усе 😁.

Це проста реалізація функції debounce:

function debounce(func, timeout = 300){
  let timer;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => { func.apply(this, args); }, timeout);
  };
}
function saveInput(){
  console.log('Saving data');
}
const processChange = debounce(() => saveInput());

Її можна використовувати для введення:

<input type="text" onkeyup="processChange()" />

Або для кнопки:

<button onclick="processChange()">Click me</button>

Або для події у вікні:

window.addEventListener("scroll", processChange);

І для інших елементів (наприклад, простих функцій JS).

Отже, що тут відбувається? debounce — це спеціальна функція, яка виконує два завдання:

  • виділення області для змінної timer
  • планування запуску функції в певний час

Давайте пояснимо, як це працює в першому випадку використання з введенням тексту.

Коли відвідувач пише першу літеру та відпускає кнопку, то debounce спочатку скидає таймер за допомогою clearTimeout(timer). На даний момент крок непотрібний, оскільки ще нічого не заплановано. Потім вона планує виклик наданої функції saveInput(), яка буде викликана через 300 мс.

Але, припустімо, що відвідувач продовжує писати, тому кожне відпускання кнопки знову викликає функцію debounce. Кожен виклик потребує скидання таймера, або, іншими словами, скасування попередніх планів saveInput(), і перепланування на новий час — 300 мс у майбутньому. Це триває до тих пір, поки відвідувач продовжує натискати кнопки.

Останнє планування виклику наданої функції saveInput() не буде очищено, і тому її зрештою буде викликано.

І навпаки: як ігнорувати подальші виклики

Це все добре для запуску автозбереження або відображення пропозицій. Але як щодо випадку використання з кількома натисканнями однієї кнопки? Ми не хочемо чекати останнього натиску, а краще зареєструвати перший і ігнорувати решту:

function debounce_leading(func, timeout = 300){
  let timer;
  return (...args) => {
    if (!timer) {
      func.apply(this, args);
    }
    clearTimeout(timer);
    timer = setTimeout(() => {
      timer = undefined;
    }, timeout);
  };
}

Тут ми запускаємо функцію saveInput() під час першого виклику функції debounce_leading, яка у свою чергу ініціалізується першим натисканням кнопки. Плануємо знищення таймера на 300 мс. Кожне наступне натискання кнопки протягом цього періоду часу вже матиме визначений таймер і зсуватиме деструкцію лише на 300 мс у майбутнє.

Реалізації debounce в бібліотеках

У цій статті я показав вам, як реалізувати функцію debounce у JavaScript і використовувати її для, ну, debounce подій, викликаних елементами вебсайту.

Однак вам не потрібно використовувати власну реалізацію debounce у своїх проєктах, якщо ви цього не хочете. Широко використовувані бібліотеки JS вже містять її реалізацію:

LibraryExample
jQuery (via library)$.debounce(300, saveInput);
Lodash_.debounce(saveInput, 300);
Underscore_.debounce(saveInput, 300);