> Как реализовать декоратор функции в JavaScript, ограничивающий число вызовов и вызывающий колбэк при последнем вызове (JavaScript)
Уровень: senior · Роль: frontend · Категория: Технические вопросы
Компании: Яндекс
Стек: JavaScript
> Пример ответа
Короткий ответ
Декоратор onceWithCallback принимает функцию и колбэк, возвращает новую функцию, которая отслеживает количество вызовов через замыкание. При каждом вызове счётчик увеличивается, и после достижения лимита (или при последнем вызове) вызывается колбэк. Реализация использует apply для передачи контекста и аргументов, а также флаг для блокировки дальнейших вызовов.
Подробное объяснение
Основная задача - создать декоратор, который:
- Ограничивает количество вызовов целевой функции (например, до N раз).
- При каждом вызове выполняет целевую функцию.
- При достижении лимита (последний разрешённый вызов) вызывает колбэк, передавая ему результат последнего вызова и контекст.
Ключевые механизмы:
- Замыкание для хранения счётчика вызовов и флага блокировки.
Function.prototype.applyдля корректной передачиthisи аргументов.- Возврат новой функции, которая при каждом вызове проверяет счётчик.
Варианты реализации:
- С фиксированным лимитом вызовов.
- С динамическим лимитом, передаваемым как аргумент декоратора.
- С поддержкой асинхронных функций (через
async/await).
Trade-off: при использовании стрелочных функций теряется контекст this, поэтому нужна обычная функция или явная привязка.
На практике
В реальных проектах такой декоратор полезен для:
- Лимитирования запросов к API (rate limiting).
- Ограничения обработчиков событий (например, кликов).
- Логирования последнего вызова в цепочке.
Пример использования:
JAVASCRIPTconst limitedLog = onceWithCallback(console.log, 3, (result) => {console.log('Последний вызов, результат:', result);});
Важно учитывать:
- Если целевая функция асинхронная, колбэк должен вызываться после разрешения промиса.
- Для отмены декоратора можно добавить метод
cancel. - В среде Node.js можно использовать
util.deprecateдля аналогичных целей.
Пример кода
JAVASCRIPTfunction onceWithCallback(fn, limit, callback) {let callCount = 0;let isBlocked = false;let lastResult;return function(...args) {if (isBlocked) return;callCount++;const context = this;if (callCount <= limit) {lastResult = fn.apply(context, args);}if (callCount === limit) {isBlocked = true;callback.call(context, lastResult);}return lastResult;};}// Пример использованияconst originalFn = (x) => x * 2;const decorated = onceWithCallback(originalFn, 3, (result) => {console.log('Последний результат:', result);});console.log(decorated(1)); // 2console.log(decorated(2)); // 4console.log(decorated(3)); // 6, затем "Последний результат: 6"console.log(decorated(4)); // undefined (блокировано)
Как отвечать на собеседовании
- Начни с краткого определения задачи и её практической ценности.
- Объясни механизм замыкания для хранения состояния.
- Покажи базовую реализацию, затем обсуди улучшения (асинхронность, отмена).
- Упомяни edge cases: потеря контекста, передача
this, работа сnull/undefined. - Если спросят про альтернативы - упомяни библиотеки (lodash.once, throttle/debounce).
Что проверяет интервьюер
- Понимание замыканий и области видимости.
- Умение работать с контекстом (
this) и методамиapply/call. - Навыки обработки граничных случаев (лимит 0, отрицательный лимит).
- Способность проектировать гибкие API (колбэк, опции).
- Знание разницы между синхронными и асинхронными функциями.
Типичные ошибки
- Использование стрелочной функции для декоратора - теряется
this. - Неучёт передачи аргументов через
argumentsили rest-параметры. - Вызов колбэка до выполнения целевой функции (если важна последовательность).
- Отсутствие блокировки после достижения лимита - функция продолжает вызываться.
- Неправильная обработка
undefinedрезультата при лимите 0.
> Похожие задачи по frontend
Как обработать ошибку, если хотя бы один из нескольких запросов не ответил в JavaScript?
Как обрабатывать массив результатов промисов в JavaScript и выводить success или fail в консоль
Что содержит this в обработчике события на кнопку в JavaScript
Как реализовать тайм-аут для асинхронного запроса, если ответ приходит дольше одной секунды в JavaScript?
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью