Token Bucket Rate Limiter Async

Implement an async token bucket limiter with burst capacity, refill rate, per-key isolation, and wait semantics for queue-friendly callers.

TypescriptResilience

Code

snippet
// async token bucket rate limiter with jitter
class TokenBucket {
  private tokens: number;
  private lastRefill: number;

  constructor(
    private readonly capacity: number,
    private readonly refillPerSecond: number,
  ) {
    this.tokens = capacity;
    this.lastRefill = performance.now();
  }

  private refill(now: number) {
    const elapsed = (now - this.lastRefill) / 1000;
    this.tokens = Math.min(
      this.capacity,
      this.tokens + elapsed * this.refillPerSecond,
    );
    this.lastRefill = now;
  }

  tryTake(count = 1): boolean {
    this.refill(performance.now());
    if (this.tokens < count) return false;
    this.tokens -= count;
    return true;
  }

  async take(count = 1): Promise<void> {
    while (!this.tryTake(count)) {
      const deficit = count - this.tokens;
      const waitMs = (deficit / this.refillPerSecond) * 1000;
      await new Promise((r) => setTimeout(r, Math.max(waitMs, 10)));
    }
  }
}

const buckets = new Map<string, TokenBucket>();

function bucketFor(key: string) {
  let b = buckets.get(key);
  if (!b) {
    b = new TokenBucket(10, 5);
    buckets.set(key, b);
  }
  return b;
}

await bucketFor('api:upload').take(3);
console.log('upload quota acquired, burst-friendly');
const immediate = bucketFor('api:upload').tryTake(8);
console.log('immediate take of 8:', immediate);