← Назад к вопросам
Реализуйте функцию debounce
2.0 Middle🔥 171 комментариев
#Node.js и JavaScript#Алгоритмы и структуры данных
Условие
Напишите функцию debounce(fn, delay), которая возвращает новую функцию, откладывающую вызов fn до тех пор, пока не пройдет delay миллисекунд с момента последнего вызова.
function debounce(fn, delay) {
// Ваш код
}
// Пример использования:
const debouncedLog = debounce((x) => console.log(x), 300);
debouncedLog(1);
debouncedLog(2);
debouncedLog(3); // Только это значение будет выведено через 300мс
Что проверяется
- Работа с таймерами (setTimeout, clearTimeout)
- Замыкания в JavaScript
- Практическое понимание паттерна debounce
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Решение
Базовая реализация
function debounce<T extends (...args: any[]) => any>(fn: T, delay: number): (...args: Parameters<T>) => void {
let timerId: NodeJS.Timeout | null = null;
return function (...args: Parameters<T>) {
// Отменяем предыдущий таймер
if (timerId !== null) {
clearTimeout(timerId);
}
// Устанавливаем новый таймер
timerId = setTimeout(() => {
fn(...args);
timerId = null;
}, delay);
};
}
Как это работает
Ключевой механизм — замыкание (Closure):
- Переменная
timerIdсохраняется в замыкании возвращаемой функции - Каждый вызов debounced функции имеет доступ к одному и тому же
timerId - Это позволяет отменять предыдущие таймеры
Порядок выполнения:
debouncedLog(1)→timerId = setTimeout(..., 300)debouncedLog(2)→clearTimeout(timerId)→ новый таймер на 300мсdebouncedLog(3)→clearTimeout(timerId)→ новый таймер на 300мс- Через 300мс:
fn(3)→ вывод3
Пример использования
const debouncedLog = debounce((x: number) => console.log(x), 300);
debouncedLog(1);
debouncedLog(2);
debouncedLog(3);
// Через 300мс выведет: 3
Реальный пример: поиск в API
const searchAPI = (query: string) => {
console.log(`Поиск: ${query}`);
// API запрос
};
const debouncedSearch = debounce(searchAPI, 500);
input.addEventListener('input', (e) => {
debouncedSearch(e.target.value);
// Запрос отправится только через 500мс после последнего символа
});
Версия с возвращаемым значением
function debounce<T extends (...args: any[]) => any>(
fn: T,
delay: number
): (...args: Parameters<T>) => Promise<ReturnType<T>> {
let timerId: NodeJS.Timeout | null = null;
return function (...args: Parameters<T>): Promise<ReturnType<T>> {
return new Promise((resolve) => {
if (timerId !== null) {
clearTimeout(timerId);
}
timerId = setTimeout(() => {
const result = fn(...args);
resolve(result);
timerId = null;
}, delay);
});
};
}
Версия с методом cancel
function debounce<T extends (...args: any[]) => any>(fn: T, delay: number) {
let timerId: NodeJS.Timeout | null = null;
const debounced = function (...args: Parameters<T>) {
if (timerId !== null) {
clearTimeout(timerId);
}
timerId = setTimeout(() => {
fn(...args);
timerId = null;
}, delay);
};
debounced.cancel = () => {
if (timerId !== null) {
clearTimeout(timerId);
timerId = null;
}
};
return debounced;
}
const debouncedSearch = debounce(searchAPI, 500);
window.addEventListener('beforeunload', () => debouncedSearch.cancel());
Тестирование
import { describe, it, expect, vi } from 'vitest';
describe('debounce', () => {
it('должен задержать вызов функции', () => {
vi.useFakeTimers();
const fn = vi.fn();
const debounced = debounce(fn, 300);
debounced('hello');
expect(fn).not.toHaveBeenCalled();
vi.advanceTimersByTime(299);
expect(fn).not.toHaveBeenCalled();
vi.advanceTimersByTime(1);
expect(fn).toHaveBeenCalledWith('hello');
expect(fn).toHaveBeenCalledTimes(1);
});
it('должен сбросить таймер при повторном вызове', () => {
vi.useFakeTimers();
const fn = vi.fn();
const debounced = debounce(fn, 300);
debounced(1);
vi.advanceTimersByTime(100);
debounced(2);
vi.advanceTimersByTime(100);
debounced(3);
vi.advanceTimersByTime(300);
expect(fn).toHaveBeenCalledWith(3);
expect(fn).toHaveBeenCalledTimes(1);
});
});
Debounce vs Throttle
| Aspekt | Debounce | Throttle |
|---|---|---|
| Суть | Откладывает вызов до затишья | Ограничивает частоту вызовов |
| Используется | Search input, resize events | Scroll, mouse move |
| Результат | Вызов только после паузы | Максимум N раз в секунду |
Практические применения
- Поиск с автодополнением — не отправлять запрос на каждый символ
- Проверка уникальности username — проверять только после паузы
- Сохранение черновика — сохранять через 5 сек после последнего изменения
- Resize/Scroll обработчики — пересчитывать размеры после окончания resizing
- Валидация формы — проверять поле только после паузы в вводе