Промпт: объясни, что делает код
Понятное объяснение чужого кода по шагам. Готовый промт на русском: скопируйте, подставьте свои данные и вставьте в GigaChat, YandexGPT, ChatGPT, Claude или Gemini.
Текст промпта
Что подставить в промт
| Поле | Пример |
|---|---|
| {reader} | начинающего разработчика |
| {code} | const debounce = (fn, ms) => { let t; return (...a) => { clearTimeout(t); t = setTimeout(() => fn(...a), ms) } } |
Ответы нейросетей на этот промпт
Кратко:
Функция debounce ограничивает количество вызовов другой функции до одного раза каждые заданный промежуток времени (ms), игнорируя промежуточные вызовы между этими интервалами.
По блокам:
- Объявление функции
Функция принимает два аргумента — саму функцию (fn) и задержку в миллисекундах (ms). Она возвращает новую функцию-обёртку над переданной функцией.
- Использование замыкания:
Переменная t, хранящая идентификатор таймера, доступна внутри возвращаемой анонимной функции благодаря замыканию.
- Работа внутренней функции:
Когда внутренняя функция вызвана:
- Очищается предыдущий таймер через
clearTimeout. - Устанавливается новый таймер через
setTimeout. Внутри таймера выполняется исходная функция (fn), передавая ей аргументы вызова (...a).
- Механизм дебаунса:
Если функция вызвана повторно до истечения предыдущего интервала ожидания, предыдущий таймер сбрасывается, и запускается новый отсчёт задержки.
Неочевидные моменты и подводные камни:
- Возвращаемая функция сохраняет состояние последнего вызова. Это значит, что параметры предыдущих вызовов теряются после каждого нового запуска.
- Важно понимать контекст выполнения функций. Например, при передаче метода объекта нужно использовать стрелочную функцию или привязывать контекст вручную, иначе будет потеряна ссылка на объект.
- Если задержка установлена слишком маленькой, эффект от дебаунса практически пропадает.
- Дебаусинг удобен для оптимизации обработки событий вроде скролла, изменения размеров окна браузера, ввода текста и пр., где события генерируются часто, но нужны редкие обновления интерфейса.
Проверки работы:
- Тест 1: проверка базовой функциональности
let count = 0;
const incrementCount = () => {
console.log('incremented');
count++;
};
const delayedIncrement = debounce(incrementCount, 500);
delayedIncrement(); // Сразу же вызовем первый раз
console.log(count); // Должно вывести 0
// Ждём больше половины секунды
setTimeout(() => {
delayedIncrement();
}, 600);
// Ещё подождем секунду и проверим результат
setTimeout(() => {
console.log(count); // Должно вывести 1
}, 1700);
- Тест 2: несколько быстрых последовательных вызовов
let calls = [];
const logCallTime = () => {
calls.push(Date.now());
};
const throttledLog = debounce(logCallTime, 1000);
throttledLog(); // Вызываем сразу
throttledLog(); // Через 50 мс
throttledLog(); // Через ещё 100 мс
throttledLog(); // И снова спустя 150 мс
// Подождать немного дольше интервала
setTimeout(() => {
console.log(calls.length); // Должно быть ровно одно значение
}, 1200);
- Тест 3: проверка сброса таймера при повторных вызовах
let loggedValue = '';
const logInput = value => {
loggedValue += value + ' ';
};
const throttledLogger = debounce(logInput, 800);
throttledLogger('Привет'); // Первый вызов
setTimeout(() => throttledLogger('Мир'), 900); // Второй вызов должен перезаписать таймер первого
setTimeout(() => throttledLogger('Чао'), 1500); // Третий вызов уже сработает отдельно
setTimeout(() => console.log(loggedValue.trim()), 2500); // Должно напечататься только 'Мир Чао'
Эти тесты помогут убедиться, что механизм дебаунса корректно снижает частоту вызовов и правильно обрабатывает ситуации быстрого следования нескольких вызовов подряд.
Одной фразой: этот код создаёт функцию-обёртку, которая откладывает вызов fn и выполняет его только после того, как в течение ms миллисекунд обёртку больше не вызывали.
Код
const debounce = (fn, ms) => {
let t;
return (...a) => {
clearTimeout(t);
t = setTimeout(() => fn(...a), ms);
};
};
По блокам
1. Объявление функции debounce
const debounce = (fn, ms) => {
Здесь создаётся функция debounce.
Она принимает два аргумента:
fn— функция, которую нужно вызывать с задержкой;ms— задержка в миллисекундах.
Пример использования:
const debouncedSearch = debounce(search, 500);
Это значит: функция search будет вызвана только спустя 500 мс после последнего вызова debouncedSearch.
2. Переменная для таймера
let t;
Здесь создаётся переменная t.
В неё будет сохраняться идентификатор таймера, который возвращает setTimeout.
Она нужна, чтобы потом можно было отменить предыдущий таймер:
clearTimeout(t);
Важно: переменная t живёт внутри debounce, но доступна возвращаемой функции. Это называется замыкание.
3. Возврат новой функции
return (...a) => {
debounce возвращает новую функцию.
Эта новая функция будет вызываться вместо оригинальной fn.
Например:
const debouncedLog = debounce(console.log, 1000);
debouncedLog('hello');
debouncedLog('world');
В этом случае напрямую console.log не вызывается. Вызывается обёртка, которая решает, когда вызвать оригинальную функцию.
4. Сбор всех аргументов
(...a) => {
...a — это rest-параметр.
Он собирает все переданные аргументы в массив a.
Например:
debouncedLog('hello', 123, true);
Тогда:
a = ['hello', 123, true]
Это нужно, чтобы потом передать эти же аргументы в оригинальную функцию fn.
5. Отмена предыдущего таймера
clearTimeout(t);
Каждый раз при вызове обёртки код отменяет предыдущий запланированный вызов.
Если таймер ещё не успел сработать, он будет отменён.
Например:
debouncedFn();
debouncedFn();
debouncedFn();
Если все три вызова произошли быстро, первые два таймера будут отменены, и выполнится только последний.
Если t ещё undefined, ничего страшного не произойдёт: clearTimeout(undefined) безопасен.
6. Создание нового таймера
t = setTimeout(() => fn(...a), ms);
Здесь создаётся новый таймер.
Он говорит: через ms миллисекунд вызвать функцию fn с аргументами a.
Например:
const debouncedLog = debounce(console.log, 1000);
debouncedLog('test');
Через 1 секунду выполнится:
console.log('test');
Что в итоге происходит
Допустим, есть такой код:
const save = debounce(() => {
console.log('Сохраняем...');
}, 1000);
save();
save();
save();
Каждый вызов save() отменяет предыдущий таймер и запускает новый.
Если вызовы идут подряд, то реально выполнится только один console.log, через 1 секунду после последнего вызова save().
Где это используется
debounce часто используют там, где событие может происходить очень часто:
Поиск при вводе текста
input.addEventListener(
'input',
debounce((event) => {
console.log('Ищем:', event.target.value);
}, 500)
);
Функция поиска вызовется только когда пользователь перестал печатать на 500 мс.
Изменение размера окна
window.addEventListener(
'resize',
debounce(() => {
console.log('Размер окна изменился');
}, 300)
);
Обработчик не будет вызываться десятки раз в секунду.
Неочевидные места и подводные камни
1. Это именно debounce, а не throttle
debounce ждёт паузу между вызовами.
Если функция вызывается постоянно без паузы, fn может не выполниться вообще, пока вызовы не прекратятся.
Например:
const f = debounce(() => console.log('run'), 1000);
setInterval(f, 500);
Так как f вызывается каждые 500 мс, таймер всё время сбрасывается. console.log('run') не выполнится.
2. Функция выполняется только после задержки
Этот вариант debounce срабатывает на “заднем фронте” — после окончания серии вызовов.
Он не вызывает fn сразу при первом вызове.
То есть:
const f = debounce(() => console.log('run'), 1000);
f();
console.log появится только через 1 секунду.
3. Теряется this
В коде используется стрелочная функция:
return (...a) => {
и потом:
fn(...a)
Если fn зависит от this, могут быть проблемы.
Например:
const obj = {
name: 'Alex',
sayName() {
console.log(this.name);
}
};
const debounced = debounce(obj.sayName, 500);
debounced();
Через 500 мс this внутри sayName может быть потерян, и this.name будет undefined.
Более универсальный вариант мог бы выглядеть так:
const debounce = (fn, ms) => {
let t;
return function (...a) {
clearTimeout(t);
t = setTimeout(() => fn.apply(this, a), ms);
};
};
Здесь используется обычная функция, чтобы сохранить this.
4. Нет возможности отменить таймер снаружи
В этом варианте нельзя сделать так:
debounced.cancel();
Иногда это полезно, например при размонтировании компонента в React или Vue.
Расширенная версия могла бы иметь метод cancel.
5. Возвращаемое значение fn теряется
Если fn что-то возвращает:
const getValue = () => 123;
const debounced = debounce(getValue, 500);
const result = debounced();
result не будет равен 123.
Почему? Потому что fn вызывается позже, асинхронно, через setTimeout.
6. Исключения произойдут позже
Если внутри fn будет ошибка, она произойдёт не в момент вызова обёртки, а позже — когда сработает таймер.
const f = debounce(() => {
throw new Error('Ошибка');
}, 500);
f();
Ошибка появится через 500 мс.
Как проверить, что код работает
Тест 1. Один вызов выполняется после задержки
const fn = jest.fn();
const debounced = debounce(fn, 100);
debounced();
jest.advanceTimersByTime(99);
expect(fn).not.toHaveBeenCalled();
jest.advanceTimersByTime(1);
expect(fn).toHaveBeenCalledTimes(1);
Проверяем, что функция не вызывается сразу, а вызывается только после нужной задержки.
Тест 2. Несколько быстрых вызовов превращаются в один
const fn = jest.fn();
const debounced = debounce(fn, 100);
debounced();
debounced();
debounced();
jest.advanceTimersByTime(100);
expect(fn).toHaveBeenCalledTimes(1);
Проверяем основную идею debounce: много быстрых вызовов дают один реальный вызов.
Тест 3. Используются аргументы последнего вызова
const fn = jest.fn();
const debounced = debounce(fn, 100);
debounced('first');
debounced('second');
debounced('third');
jest.advanceTimersByTime(100);
expect(fn).toHaveBeenCalledWith('third');
Проверяем, что в оригинальную функцию передаются аргументы именно последнего вызова.
Что делает код целиком
Это функция debounce, которая "откладывает" вызов другой функции, пока не пройдёт пауза без новых вызовов — полезно, например, чтобы не дёргать поиск на каждое нажатие клавиши.
Разбор по блокам
const debounce = (fn, ms) => { ... }
Создаём функцию debounce, которая принимает:
fn— функцию, которую нужно "притормозить";ms— задержку в миллисекундах.
Она возвращает новую функцию — "обёртку" над fn.
let t;
Переменная для хранения идентификатора таймера (setTimeout). Благодаря замыканию (closure) она "живёт" между вызовами обёртки и не создаётся заново каждый раз.
return (...a) => {
Возвращаемая функция принимает любое количество аргументов (...a — rest-параметр, собирает их в массив).
clearTimeout(t);
Отменяем предыдущий запланированный вызов, если он ещё не сработал. Это и есть суть debounce: если нас дёргают снова — сбрасываем старый таймер.
t = setTimeout(() => fn(...a), ms)
Запускаем новый таймер: через ms миллисекунд вызовется fn с последними переданными аргументами (...a — spread, распаковывает массив обратно в аргументы).
Неочевидные места и подводные камни
- Контекст
thisне передаётся. Еслиfn— метод объекта и используетthis, он может сломаться, т.к. вызывается как обычная функция внутри стрелочной() => fn(...a). Нужно использоватьfn.apply(this, a)и обычнуюfunction, если важен контекст.
- Один
debounce= один общий таймер на всех, кто его вызывает. Если обернуть одну функцию и использовать её для разных элементов (например, разных input'ов), они будут "мешать" друг другу — сбрасывать таймер друг у друга.
- Нет возможности отменить вручную (например, при размонтировании компонента в React). Хорошо бы добавить метод
.cancel().
- Нет варианта "вызвать сразу первый раз" (immediate/leading edge) — функция сработает только после паузы, а не в момент первого вызова.
- Возвращаемое значение
fnтеряется —setTimeoutигнорирует то, что возвращаетfn, так что debounce нельзя использовать с функциями, результат которых нужен синхронно (это и логично, учитывая асинхронную природу).
Как бы я проверил, что код работает
- Базовая задержка:
const log = debounce((msg) => console.log(msg), 100);
log('a'); log('b'); log('c');
// Ожидание: через 100мс в консоли появится только 'c'
- Передача нескольких аргументов:
const sum = debounce((a, b) => console.log(a + b), 50);
sum(2, 3);
// Через 50мс: 5
- Использование с таймером (через
jest.useFakeTimers()в тестах):
jest.useFakeTimers();
const fn = jest.fn();
const d = debounce(fn, 200);
d();
d();
jest.advanceTimersByTime(199);
expect(fn).not.toHaveBeenCalled(); // ещё не вызвана
jest.advanceTimersByTime(1);
expect(fn).toHaveBeenCalledTimes(1); // теперь вызвана один разОтлично, давай разберем этот код для начинающего разработчика!
Что делает код целиком (одной фразой):
Этот код создает функцию, которая откладывает выполнение другой функции до тех пор, пока не пройдет определенное время без новых вызовов.
По блокам: что происходит и зачем
Давай разберем код по частям:
const debounce = (fn, ms) => {
// ...
}
const debounce = (fn, ms) => { ... }:- Мы объявляем константу
debounce, которая будет хранить нашу "функцию-фабрику" (функцию, которая создает другие функции). - Эта функция
debounceпринимает два аргумента: fn: Это та функция, которую мы хотим "замедлить" или "отложить". Например, функция, которая отправляет запрос на сервер при вводе текста в поле поиска.ms: Это количество миллисекунд (время), которое должно пройти без новых вызововfn, прежде чем она будет фактически выполнена.
let t;
let t;:- Внутри
debounceмы объявляем переменнуюt. - Эта переменная будет хранить идентификатор таймера, который возвращает функция
setTimeout. - Мы используем
let, потому что значениеtбудет меняться (перезаписываться) при каждом новом вызове. - Зачем? Нам нужен способ "отменить" предыдущий таймер, если функция была вызвана снова до того, как предыдущий таймер успел сработать.
clearTimeout(t)делает именно это, а для этого ему нужен идентификатор таймера.
return (...a) => {
// ...
}
return (...a) => { ... }:- Функция
debounceвозвращает новую функцию. Это очень важный момент! - Эту возвращаемую функцию мы будем вызывать вместо оригинальной
fn. (...a): Это синтаксис "rest-параметров". Он означает, что эта возвращаемая функция может принимать любое количество аргументов. Все эти аргументы будут собраны в массивa.- Зачем? Когда мы "дебаунсим" функцию, мы хотим, чтобы она все еще могла принимать свои оригинальные аргументы. Например, если
fnпринимаетeventобъект, то возвращаемая функция тоже должна его принимать и передавать дальше.
clearTimeout(t);
clearTimeout(t);:- При каждом вызове возвращаемой функции (той, что мы используем вместо оригинальной
fn), мы сначала отменяем любой предыдущий таймер, который мог быть установлен. - Если
tеще не был установлен (первый вызов) или предыдущий таймер уже сработал,clearTimeout(undefined)илиclearTimeout(сработавший_таймер_id)просто ничего не сделает, что безопасно. - Зачем? Это ключевая часть "дебаунса". Если пользователь быстро печатает, мы не хотим, чтобы функция срабатывала после каждой буквы. Мы хотим, чтобы она сработала только после того, как пользователь перестанет печатать на
msмиллисекунд. Отмена предыдущего таймера при каждом новом вызове гарантирует, что таймер всегда будет "сбрасываться", если вызовы идут слишком часто.
t = setTimeout(() => fn(...a), ms)
t = setTimeout(() => fn(...a), ms):- После отмены предыдущего таймера, мы устанавливаем новый таймер.
setTimeout(() => fn(...a), ms): Это говорит JavaScript: "Черезmsмиллисекунд выполни вот эту анонимную функцию".() => fn(...a): Анонимная функция, которая будет выполнена черезmsмиллисекунд. Внутри нее мы вызываем нашу оригинальную функциюfn, передавая ей все аргументыa, которые были получены возвращаемой функцией....aздесь — это синтаксис "spread-оператора", который "разворачивает" массивaв отдельные аргументы дляfn.t = ...: Идентификатор этого нового таймера сохраняется в переменнойt, чтобы его можно было отменить при следующем вызове.- Зачем? Это устанавливает отложенное выполнение
fn. Если до истеченияmsмиллисекунд будет новый вызов, этот таймер будет отменен, и будет установлен новый, тем самым "сбрасывая" отсчет времени.
Неочевидные места и возможные подводные камни
- Замыкание (Closure): Это самый важный и, возможно, самый сложный для понимания аспект для новичков.
- Когда
debounceвызывается, она создает переменнуюt. - Затем она возвращает новую функцию.
- Эта возвращаемая функция "помнит" и имеет доступ к переменной
t, даже после того, какdebounceзакончила свое выполнение. Это называется замыканием. - Подводный камень: Если вы попытаетесь перенести
let t;внутрь возвращаемой функции, тоtбудет объявляться заново при каждом вызове, иclearTimeout(t)всегда будет отменятьundefinedили только что созданный таймер, который еще не успел сработать, и дебаунс не будет работать.tдолжен быть вне возвращаемой функции, но внутриdebounce.
thisконтекст:- В текущей реализации, когда
fnвызывается внутриsetTimeout, она будет выполняться сthisконтекстом глобального объекта (в браузере этоwindow, в Node.js этоundefinedв строгом режиме). - Если ваша
fnзависит от определенногоthisконтекста (например, если это метод объекта, который вы хотите вызвать какmyObject.myMethod()), то этот код не сохранитthisконтекст. - Решение: Чтобы сохранить
thisконтекст, можно использоватьfn.apply(this, a)илиfn.call(this, ...a)вместоfn(...a).
- Несколько дебаунс-функций:
- Каждый раз, когда вы вызываете
debounce(someFn, 500), вы получаете новую дебаунс-функцию с собственной переменнойt. Это хорошо и ожидаемо. - Например:
``javascript``
const debouncedSearch = debounce(searchFunction, 300);
const debouncedResize = debounce(resizeFunction, 500);
// debouncedSearch и debouncedResize имеют независимые таймеры.
- Синхронность/Асинхронность:
debounceсам по себе является синхронной функцией (она сразу возвращает другую функцию).- Однако, функция, которую она возвращает, использует
setTimeout, что делает ее выполнение асинхронным. Это означает, что код после вызова дебаунс-функции продолжит выполняться, аfnсработает только позже.
Как бы ты проверил, что код работает (2-3 теста)
Для проверки можно использовать простой HTML-файл с JavaScript или Node.js окружение.
- Базовый тест: Однократный вызов (проверка отложенного выполнения)
- Цель: Убедиться, что функция
fnдействительно вызывается черезmsмиллисекунд, если нет других вызовов. - Сценарий:
```javascript
const log = (msg) => console.log(msg);
const debouncedLog = debounce(log, 500);
console.log("Начало теста 1");
debouncedLog("Привет!"); // Должно сработать через 500мс
console.log("Конец теста 1");
// Ожидаемый вывод:
// Начало теста 1
// Конец теста 1
// (через 500мс) Привет!
```
- Проверка: Визуально или с помощью
jest.useFakeTimers()иjest.advanceTimersByTime(500)в тестовом фреймворке.
- Тест на "отмену" (проверка сброса таймера)
- Цель: Убедиться, что если функция вызывается несколько раз подряд быстрее, чем
ms, тоfnсрабатывает только один раз после последнего вызова. - Сценарий:
``javascriptФункция вызвана. Всего: ${callCount}`);
let callCount = 0;
const incrementCallCount = () => {
callCount++;
console.log(
};
const debouncedIncrement = debounce(incrementCallCount, 300);
console.log("Начало теста 2");
debouncedIncrement(); // 1
debouncedIncrement(); // 2 - отменяет 1
debouncedIncrement(); // 3 - отменяет 2
setTimeout(() => debouncedIncrement(), 100); // 4 - отменяет 3
setTimeout(() => debouncedIncrement(), 200); // 5 - отменяет 4
// Ожидаем, что сработает только один раз, через 300мс после последнего вызова (который был на 200мс).
// То есть, примерно на 500мс от начала.
console.log("Конец теста 2");
setTimeout(() => {
console.log(Финальное количество вызовов: ${callCount});
}, 1000); // Проверяем через достаточное время
// Ожидаемый вывод:
// Начало теста 2
// Конец теста 2
// (примерно через 500мс) Функция вызвана. Всего: 1
// (через 1000мс) Финальное количество вызовов: 1
```
- Проверка: Убедиться, что
incrementCallCountбыла вызвана ровно один раз.
- Тест на передачу аргументов
- Цель: Убедиться, что аргументы правильно передаются оригинальной функции
fn. - Сценарий:
``javascript${greeting}, ${name}!`);
const greet = (name, greeting) => console.log(
const debouncedGreet = debounce(greet, 200);
console.log("Начало теста 3");
debouncedGreet("Мир", "Привет"); // Отменится
debouncedGreet("Разработчик", "Здравствуй"); // Должно сработать
console.log("Конец теста 3");
// Ожидаемый вывод:
// Начало теста 3
// Конец теста 3
// (через 200мс) Здравствуй, Разработчик!
```
- Проверка: Убедиться, что в консоли появилось правильное приветствие с правильными аргументами.
Эти тесты охватывают основные аспекты работы debounce и помогут убедиться, что он работает как задумано.
Советы
- Для большого проекта давайте модели и структуру папок, тогда она объяснит связи между файлами.
- Скопируйте промпт кнопкой выше.
- Замените поля в фигурных скобках своими данными.
- Отправьте в нейросеть и сравните ответ с примером на этой странице.
Подробнее о структуре хорошего запроса: гид AI University.
Похожие промпты
Промпты работают лучше, когда понимаешь модель
56 официальных курсов Anthropic, OpenAI, Google и других на русском: первые уроки бесплатно, без VPN. Наборы промптов входят в полный доступ.