JAVASCRIPT
Managing Client-Side API Rate Limits with a Request Queue
Gracefully handle 429 Too Many Requests errors from APIs by implementing a client-side request queue that automatically retries requests after a delay.
class ApiRateLimiter {
constructor(maxRequestsPerPeriod, periodMs) {
this.maxRequests = maxRequestsPerPeriod;
this.periodMs = periodMs;
this.requestQueue = [];
this.inFlightRequests = 0;
this.lastResetTime = Date.now();
this.requestCountInPeriod = 0;
this.isProcessing = false;
console.log(`Rate limiter initialized: ${maxRequestsPerPeriod} reqs / ${periodMs / 1000}s`);
}
async processQueue() {
if (this.isProcessing || this.requestQueue.length === 0) {
return;
}
this.isProcessing = true;
while (this.requestQueue.length > 0) {
const now = Date.now();
// Reset count if a new period has started
if (now - this.lastResetTime >= this.periodMs) {
this.requestCountInPeriod = 0;
this.lastResetTime = now;
}
if (this.requestCountInPeriod < this.maxRequests) {
const { requestFn, resolve, reject } = this.requestQueue.shift();
this.requestCountInPeriod++;
this.inFlightRequests++;
console.log(`Processing request. Current count: ${this.requestCountInPeriod}/${this.maxRequests}`);
try {
const result = await requestFn();
resolve(result);
} catch (error) {
if (error.response && error.response.status === 429) {
// Too Many Requests - Re-queue with a delay
console.warn('429 Too Many Requests. Re-queueing request.');
this.requestQueue.unshift({ requestFn, resolve, reject }); // Put back to front
await new Promise(r => setTimeout(r, this.periodMs / this.maxRequests + 1000)); // Wait for next period + buffer
} else {
reject(error);
}
} finally {
this.inFlightRequests--;
}
} else {
// Rate limit reached, wait for the period to reset
const timeToWait = this.periodMs - (now - this.lastResetTime);
console.log(`Rate limit reached. Waiting for ${timeToWait}ms.`);
await new Promise(r => setTimeout(r, timeToWait + 50)); // Add a small buffer
}
}
this.isProcessing = false;
console.log('Queue finished processing.');
}
// Public method to add a request to the queue
addRequest(requestFn) {
return new Promise((resolve, reject) => {
this.requestQueue.push({ requestFn, resolve, reject });
this.processQueue(); // Attempt to process immediately
});
}
}
// --- Example Usage ---
// Initialize with 5 requests per 10 seconds
const apiLimiter = new ApiRateLimiter(5, 10000);
// Mock API call function
const mockApiCall = async (id) => {
const delay = Math.random() * 500 + 100; // Simulate network latency
await new Promise(r => setTimeout(r, delay));
if (id === 3 && Math.random() < 0.8) { // Simulate a 429 for a specific request sometimes
const error = new Error('Too Many Requests');
error.response = { status: 429 };
throw error;
}
if (Math.random() < 0.1) { // Simulate other errors
throw new Error(`Failed to fetch item ${id}`);
}
return `Data for item ${id}`;
};
async function makeRequest(id) {
try {
const result = await apiLimiter.addRequest(() => mockApiCall(id));
console.log(`Success for item ${id}: ${result}`);
} catch (error) {
console.error(`Error for item ${id}:`, error.message);
}
}
// Enqueue a bunch of requests quickly
for (let i = 1; i <= 15; i++) {
makeRequest(i);
}
How it works: This JavaScript snippet demonstrates a client-side API rate limiter using a request queue. It ensures that your application doesn't exceed an API's specified request limits (e.g., `maxRequestsPerPeriod` within `periodMs`). Requests are added to a queue, and a `processQueue` method dispatches them adhering to the rate limit. If a `429 Too Many Requests` error is encountered, the request is automatically re-queued with a delay, providing a robust way to gracefully handle and recover from API rate limiting on the client side without bombarding the server.