Как ограничить количество запросов к Endpoint?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Rate Limiting в Frontend: Ограничение запросов к API
Rate limiting (ограничение частоты запросов) — критически важный паттерн для защиты API и улучшения пользовательского опыта. На frontend это реализуется несколькими способами, каждый со своей областью применения.
1. Request Debouncing и Throttling
Первый уровень защиты — предотвращение отправки множественных запросов из-за быстрых пользовательских действий (ввод текста, клики).
Debouncing откладывает запрос до окончания действия:
function useDebounce(callback, delay) {
const timeoutRef = useRef(null);
return useCallback((...args) => {
clearTimeout(timeoutRef.current);
timeoutRef.current = setTimeout(() => {
callback(...args);
}, delay);
}, [callback, delay]);
}
// Использование в компоненте поиска
const handleSearch = useDebounce((query) => {
api.search(query);
}, 300);
Throttling ограничивает частоту вызовов:
function useThrottle(callback, interval) {
const lastCallRef = useRef(null);
return useCallback((...args) => {
const now = Date.now();
if (!lastCallRef.current || now - lastCallRef.current >= interval) {
callback(...args);
lastCallRef.current = now;
}
}, [callback, interval]);
}
// Для scroll-событий
const handleScroll = useThrottle(() => {
loadMoreData();
}, 1000);
2. Client-side Rate Limiting
Ограничиваем количество одновременных запросов к конкретному endpoint:
class RateLimiter {
constructor(maxRequests = 5, windowMs = 60000) {
this.maxRequests = maxRequests;
this.windowMs = windowMs;
this.requests = [];
}
async execute(fn) {
const now = Date.now();
this.requests = this.requests.filter(time => now - time < this.windowMs);
if (this.requests.length >= this.maxRequests) {
const oldestRequest = this.requests[0];
const waitTime = this.windowMs - (now - oldestRequest);
await new Promise(resolve => setTimeout(resolve, waitTime));
return this.execute(fn);
}
this.requests.push(now);
return fn();
}
}
const searchLimiter = new RateLimiter(3, 1000);
async function searchQuestions(query) {
return searchLimiter.execute(() =>
fetch(`/api/v1/questions/search?q=${query}`)
);
}
3. Retry Logic с Exponential Backoff
Восстановление после ошибок с растущей задержкой:
async function fetchWithRetry(url, options = {}, retries = 3) {
const maxRetries = retries;
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await fetch(url, options);
if (response.ok) return response;
if (response.status === 429) {
const delay = Math.pow(2, attempt) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
} catch (error) {
const delay = Math.pow(2, attempt) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
if (attempt === maxRetries - 1) throw error;
}
}
}
4. Server-side Rate Limiting Headers
Фронтенд должен обрабатывать заголовки rate limiting от сервера:
class APIClient {
constructor() {
this.rateLimitReset = null;
}
async request(url, options = {}) {
if (this.rateLimitReset && Date.now() < this.rateLimitReset) {
const waitTime = this.rateLimitReset - Date.now();
await new Promise(resolve => setTimeout(resolve, waitTime));
}
const response = await fetch(url, options);
// Обрабатываем Rate Limit заголовки
const remaining = response.headers.get('X-RateLimit-Remaining');
const reset = response.headers.get('X-RateLimit-Reset');
if (response.status === 429) {
this.rateLimitReset = parseInt(reset) * 1000;
throw new Error('Rate limit exceeded');
}
return response;
}
}
5. Практические Рекомендации
- Debounce для поиска и фильтрации (300-500ms)
- Throttle для scroll и resize (100-1000ms)
- Client-side limits для критичных операций (загрузка файлов, платежи)
- Retry logic с экспоненциальной задержкой для сетевых ошибок
- Всегда обрабатывай заголовки Rate Limiting от сервера
- Кэшируй результаты запросов (localStorage, React Query)
Распределённый подход (frontend + backend) обеспечивает надёжность и масштабируемость приложения.