> Как обрабатывать превышение лимита вызовов функции в JavaScript и что возвращать (JavaScript)

Уровень: senior · Роль: frontend · Категория: Технические вопросы

Компании: Яндекс

Стек: JavaScript

> Пример ответа

Короткий ответ

Для обработки превышения лимита вызовов функции в JavaScript используют throttling (пропуск вызовов с фиксированным интервалом) или debouncing (отложенный вызов после паузы). Возвращать следует либо последний результат, либо специальное значение (null, undefined, Error), либо использовать callback-коллбек. Выбор зависит от контекста: для UI-событий - debounce, для API-запросов - throttle с возвратом Promise или отменой предыдущего запроса.

Подробное объяснение

Превышение лимита вызовов функции - это ситуация, когда функция вызывается чаще, чем допустимо по времени или ресурсам. В JavaScript это решается двумя основными паттернами:

  • Throttling - гарантирует выполнение не чаще одного раза за заданный интервал. Полезен для scroll/resize-событий, где нужно периодически обновлять данные.
  • Debouncing - откладывает выполнение до завершения серии вызовов. Идеален для поисковых полей, автокомплита, где важен только последний ввод.

Что возвращать:

  • Если функция асинхронная (например, fetch), возвращайте Promise, который разрешается после выполнения. При превышении лимита можно возвращать отклонённый Promise с ошибкой или последний успешный результат.
  • Для синхронных функций - возвращайте последний вычисленный результат или null, если вызов был проигнорирован.
  • В некоторых случаях (например, rate limiting API) лучше выбрасывать исключение или возвращать специальный объект с полем limited: true.

Важно учитывать:

  • При throttling с ведущим краем (leading) первый вызов выполняется сразу, последующие - с задержкой.
  • При debouncing с отменой предыдущего таймера нужно очищать setTimeout, чтобы избежать утечек памяти.
  • Для критичных операций (платежи, отправка форм) лучше блокировать кнопку на UI, а не полагаться только на программные ограничения.

На практике

В реальных проектах чаще всего используют готовые библиотеки (lodash.throttle, lodash.debounce) или встроенные решения (requestAnimationFrame для анимаций). Для API-запросов применяют комбинацию: debounce для ввода + throttle для отправки, чтобы не превысить лимиты сервера.

При реализации собственного rate limiter для функции, вызываемой из разных мест, используйте замыкание с счётчиком вызовов и временем последнего выполнения. Возвращайте объект с методами cancel() для отмены и flush() для принудительного выполнения.

Для асинхронных операций с возможным превышением лимита (например, WebSocket-сообщения) лучше использовать очередь с приоритетами и возвращать Promise, который разрешается при обработке или отклоняется при превышении лимита.

Пример кода

JAVASCRIPT
// Throttle с возвратом последнего результата
function throttle(fn, limit) {
let lastCall = 0;
let lastResult;
let timeoutId;
return function(...args) {
const now = Date.now();
const remaining = limit - (now - lastCall);
if (remaining <= 0) {
lastCall = now;
lastResult = fn.apply(this, args);
return lastResult;
}
// Возвращаем последний результат, если вызов проигнорирован
return lastResult;
};
}
// Debounce с возвратом Promise
function debounceAsync(fn, delay) {
let timeoutId;
let resolveList = [];
return function(...args) {
return new Promise((resolve) => {
clearTimeout(timeoutId);
resolveList.push(resolve);
timeoutId = setTimeout(() => {
const result = fn.apply(this, args);
resolveList.forEach(r => r(result));
resolveList = [];
}, delay);
});
};
}
// Rate limiter с возвратом ошибки
function rateLimiter(fn, maxCalls, interval) {
let calls = 0;
let resetTime = Date.now() + interval;
return function(...args) {
if (Date.now() >= resetTime) {
calls = 0;
resetTime = Date.now() + interval;
}
if (calls >= maxCalls) {
throw new Error('Rate limit exceeded');
}
calls++;
return fn.apply(this, args);
};
}

Как отвечать на собеседовании

Начните с чёткого определения проблемы: превышение лимита вызовов - это ситуация, когда функция вызывается чаще, чем позволяет контекст (UI-события, API-запросы, ресурсоёмкие вычисления). Затем объясните разницу между throttling и debouncing, приведите примеры использования каждого.

Упомяните, что возвращаемое значение зависит от сценария:

  • Для UI-событий - последний результат или null.
  • Для API-запросов - Promise с последним ответом или ошибкой.
  • Для критичных операций - блокировка на уровне UI + программное ограничение.

Покажите понимание edge cases: что делать с отменой предыдущих вызовов, как обрабатывать ошибки при превышении лимита, как тестировать такие функции.

Что проверяет интервьюер

  • Понимание асинхронности и event loop в JavaScript.
  • Умение выбирать правильный паттерн (throttle vs debounce) под задачу.
  • Знание работы с замыканиями, таймерами, Promise.
  • Навыки обработки ошибок и edge cases (отмена, очистка ресурсов).
  • Способность проектировать API для повторного использования.

Типичные ошибки

  • Путают throttling и debouncing, применяют не тот паттерн.
  • Забывают очищать setTimeout, что приводит к утечкам памяти.
  • Не учитывают контекст (this) при использовании стрелочных функций.
  • Возвращают undefined при проигнорированных вызовах, вместо последнего результата.
  • Не обрабатывают ошибки асинхронных операций внутри throttled/debounced функции.
  • Используют глобальные переменные вместо замыканий для хранения состояния.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью