


Debounce откладывает действие до паузы между вызовами. При новом вводе предыдущий таймер отменяется, и ожидание начинается заново. Для поисковой строки это позволяет обрабатывать последнюю введённую фразу, когда пользователь на короткое время перестал печатать.
В примере Mentoring.digital фильтруется небольшой локальный список. Так можно проверить поведение таймера отдельно от сетевых запросов и ошибок сервера.
export function debounce(fn, delayMs) {
let timer;
function wrapped(...args) {
const context = this;
clearTimeout(timer);
timer = setTimeout(() => {
timer = undefined;
fn.apply(context, args);
}, delayMs);
}
wrapped.cancel = () => {
clearTimeout(timer);
timer = undefined;
};
return wrapped;
}
Это вариант trailing: функция выполняется после последнего вызова в серии. Каждый вызов wrapped запоминает свои аргументы и контекст this, затем заменяет таймер. До исполнения доходит только последний неотменённый обработчик.
Задержка не означает точное время исполнения. Занятый главный поток и правила браузера могут отложить callback дольше. Число 300 в следующем примере является настройкой упражнения, а не универсальным оптимумом для любого интерфейса.
Добавьте HTML-поле с подписью и область результата:
<label for="search">Найти направление</label>
<input id="search" type="search">
<p id="result" aria-live="polite"></p>
В модульном скрипте после появления этих элементов подключите функцию:
const names = ["Python", "Java", "JavaScript", "Git"];
const input = document.querySelector("#search");
const output = document.querySelector("#result");
const update = debounce((query) => {
const normalized = query.trim().toLowerCase();
const found = names.filter(name =>
name.toLowerCase().includes(normalized)
);
output.textContent = found.join(", ") || "Ничего не найдено";
}, 300);
function onInput() {
update(input.value);
}
input.addEventListener("input", onInput);
// При удалении этого виджета из интерфейса:
function dispose() {
input.removeEventListener("input", onInput);
update.cancel();
}
Если debounce находится в другом файле, импортируйте её в этот скрипт. Вариант с export предполагает JavaScript-модуль. Функция dispose здесь показана как обработчик очистки: приложение должно вызвать её, когда действительно удаляет виджет.
Создавайте update один раз. Если вызывать debounce внутри onInput, каждый ввод создаст независимый таймер и предыдущие вызовы продолжат выполняться. Во втором независимом поле нужен отдельный экземпляр, иначе поля начнут отменять действия друг друга.
Он отменяет ещё не выполненный таймер, но не останавливает уже начатую функцию. Если callback отправляет fetch, более старый запрос всё ещё может закончиться позже нового. Для такой гонки нужен отдельный механизм отмены запроса и проверки актуальности результата.
Throttle решает другую задачу: ограничивает частоту работы при продолжающемся потоке событий. Trailing debounce может вообще не вызвать функцию, пока события идут без достаточно длинной паузы. Поэтому он не подходит без дополнительного решения там, где обновления должны происходить регулярно во время непрерывного действия.
Показанная обёртка не возвращает результат отложенной функции и не предоставляет Promise ожидания. В ней нет leading-вызова, flush и ограничения максимального ожидания. Не используйте её для обязательного сохранения данных перед закрытием страницы: пользователь может уйти раньше таймера.
Для автоматического теста используйте управляемые таймеры, если они есть в вашем тестовом наборе. Реальные задержки проверяют другой аспект и могут зависеть от нагрузки компьютера. Не утверждайте точность до миллисекунды по одному запуску.
На курсе Frontend на практических примерах такой виджет помогает разобраться с замыканиями и событиями. С Frontend-наставником можно проверить очистку обработчиков и переход к реальному поисковому API, когда локальный пример уже понятен.