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.

Need help integrating this into your project?

Our team of expert developers can help you build your custom application from scratch.

Hire DigitalCodeLabs