Как работает debounce и throttle в JavaScript и когда что использовать

7 минут чтения
Средний рейтинг статьи — 4.9

При разработке frontend-приложений часто приходится работать с событиями, которые генерируются десятки или даже сотни раз в секунду.

Например:

  • ввод текста в поле поиска
  • скроллинг страницы
  • перемещение мыши
  • изменение размеров окна браузера
  • drag-and-drop интерфейсы

Если выполнять тяжёлую логику при каждом таком событии, приложение быстро начинает тратить ресурсы впустую. Возникают лишние запросы к API, растёт нагрузка на backend, а интерфейс становится менее отзывчивым.

Для решения этой проблемы обычно используют debounce и throttle.

Несмотря на схожую цель, работают эти механизмы по-разному и подходят для разных задач.

Почему частые события становятся проблемой

Представим поисковую строку с подсказками.

Пользователь вводит запрос из десяти символов.

Без дополнительных ограничений браузер сгенерирует десять событий input, а код может отправить десять запросов на сервер.

Если одновременно работают тысячи пользователей, нагрузка растёт очень быстро.

Похожая ситуация возникает при скроллинге. Современный браузер способен генерировать десятки событий scroll в секунду. Если на каждое событие выполнять сложные вычисления или обновление интерфейса, производительность начнёт ухудшаться.

Поэтому разработчики обычно ограничивают частоту выполнения таких обработчиков.

Что такое debounce

Debounce откладывает выполнение функции до тех пор, пока события не перестанут поступать в течение определённого времени.

Каждое новое событие сбрасывает таймер ожидания.

Фактически debounce отвечает на вопрос:

«Выполнить действие только после того, как пользователь закончит что-либо делать».

Упрощённая схема выглядит так:

Событие
Событие
Событие
Событие
Пауза

Выполнение функции

Пока пользователь продолжает взаимодействие, функция не запускается.

Как работает debounce

Типичная реализация выглядит следующим образом:

function debounce(fn, delay) {
  let timer;
 
  return (...args) => {
    clearTimeout(timer);
 
    timer = setTimeout(() => {
      fn(...args);
    }, delay);
  };
}

Логика проста:

  • приходит событие
  • старый таймер удаляется
  • запускается новый таймер
  • если новых событий не появилось, функция выполняется

Таким образом серия из ста событий может привести всего к одному вызову обработчика.

Где debounce действительно полезен

Самый популярный сценарий — поиск.

Нет смысла отправлять запрос после каждой введённой буквы. Гораздо эффективнее дождаться, пока пользователь закончит вводить текст.

Также debounce часто используют для:

  • автодополнения
  • фильтрации таблиц
  • поиска по каталогу
  • сохранения черновиков
  • отправки настроек пользователя
  • валидации форм

Во всех этих случаях важен конечный результат, а не промежуточные действия.

Что такое throttle

Throttle решает другую задачу.

Он не ждёт завершения активности пользователя, а ограничивает частоту выполнения функции.

Например, можно указать, что обработчик должен запускаться не чаще одного раза в 200 миллисекунд.

Схематично это выглядит так:

События идут постоянно

Функция выполняется через фиксированные интервалы

Даже если браузер сгенерировал сто событий, функция выполнится только несколько раз.

Как работает throttle

Упрощённая реализация:

function throttle(fn, delay) {
  let waiting = false;
 
  return (...args) => {
    if (waiting) return;
 
    waiting = true;
 
    fn(...args);
 
    setTimeout(() => {
      waiting = false;
    }, delay);
  };
}

После первого вызова запускается период ожидания.

Пока этот период не закончится, новые события игнорируются.

Когда задержка истекает, функция снова может быть выполнена.

Где используют throttle

Throttle хорошо подходит для задач, где нужно регулярно получать обновления во время активности пользователя.

Типичные примеры:

  • обработка scroll
  • отслеживание положения курсора
  • drag-and-drop
  • анимации
  • ресайз окна браузера
  • визуальные индикаторы прогресса

В таких сценариях нельзя ждать завершения действия пользователя, поскольку интерфейс должен обновляться постоянно.

Главное различие между debounce и throttle

Многие разработчики путают эти механизмы, поскольку оба уменьшают количество вызовов функции.

Однако принцип работы разный.

Debounce:

Пользователь действует

Таймер постоянно сбрасывается

Активность закончилась

Функция выполнилась один раз

Throttle:

Пользователь действует

Функция выполняется регулярно

Частота вызовов ограничена

Проще говоря:

  • debounce ждёт окончания действий
  • throttle контролирует частоту во время действий

Когда выбирать debounce

Debounce обычно подходит если нужно получить итоговый результат после завершения активности пользователя.

Например:

  • поиск по сайту
  • фильтрация данных
  • сохранение формы
  • отправка запросов к API
  • автосохранение документов

Главная цель — избежать лишних операций.

Когда выбирать throttle

Throttle лучше подходит если система должна реагировать на происходящее в режиме реального времени.

Например:

  • отслеживание скроллинга
  • анимации интерфейса
  • перемещение объектов
  • визуализация данных
  • обработка жестов

Главная цель — обеспечить плавную работу без перегрузки браузера.

Что происходит в production

Ошибки в выборе debounce или throttle часто становятся заметны только под нагрузкой.

Например, отсутствие debounce в поиске может привести к тому, что один пользователь создаёт десятки запросов вместо одного.

А отсутствие throttle при обработке scroll может вызывать постоянную нагрузку на CPU и ухудшать плавность интерфейса.

Особенно заметны такие проблемы на мобильных устройствах, где ресурсы ограничены сильнее, чем на настольных компьютерах.

Поэтому оптимизация событий — это не просто вопрос красивого кода, а важная часть производительности приложения.

Нужно ли использовать готовые библиотеки

В production-проектах разработчики редко пишут debounce и throttle самостоятельно.

Чаще используются готовые реализации из популярных библиотек:

lodash.debounce
lodash.throttle

Они учитывают различные пограничные случаи и позволяют гибко настраивать поведение обработчиков.

Кроме того, современные frontend-фреймворки нередко предлагают собственные инструменты для решения аналогичных задач.

Итоги

Debounce и throttle помогают контролировать частоту выполнения функций в JavaScript и снижать нагрузку на приложение.

Несмотря на похожее назначение, используются они в разных ситуациях:

  • debounce выполняет действие после окончания активности пользователя
  • throttle выполняет действие регулярно, но с ограниченной частотой

Для поиска, фильтрации и запросов к API чаще подходит debounce.

Для scroll, drag-and-drop, анимаций и интерактивных интерфейсов обычно используют throttle.

Понимание различий между этими подходами позволяет делать интерфейсы более отзывчивыми и избегать лишней нагрузки как на браузер, так и на backend-системы.

7 минут чтения
Средний рейтинг статьи — 4.9

Настроить мониторинг за 30 секунд

Надежные оповещения о даунтаймах. Без ложных срабатываний