We need to implement a rate limiter that controls the frequency of requests or operations. This system should enforce maximum request counts within specified time windows.
// Rate Limiter Implementation with Advanced Token Bucket Strategy
class RateLimiterConfig {
constructor(maxRequests, windowMs) {
// Store the maximum number of requests allowed
this.maxRequests = maxRequests;
// Store the time window in milliseconds
this.windowMs = windowMs;
}
}
class RateLimiter {
constructor(config) {
// Initialize the configuration object for rate limiting
this.config = config;
// Initialize a map to store request metadata
this.requestMap = new Map();
// Initialize a WeakMap for internal state tracking
this.internalStateWeakMap = new WeakMap();
}
// Validate that the provided key is a valid string identifier
validateKey(key) {
if (typeof key !== 'string' || key.length === 0) {
throw new Error('Key must be a non-empty string');
}
return true;
}
// Check if the request should be allowed using token bucket algorithm
async isAllowed(key) {
try {
// Validate the key parameter
this.validateKey(key);
// Get current timestamp in milliseconds
const now = Date.now();
// Initialize request history if key does not exist
if (!this.requestMap.has(key)) {
this.requestMap.set(key, {
timestamps: [],
firstRequestTime: now
});
}
// Retrieve the request history for this key
const requestHistory = this.requestMap.get(key);
// Filter out requests outside the current window
requestHistory.timestamps = requestHistory.timestamps.filter(
timestamp => now - timestamp < this.config.windowMs
);
// Check if we have exceeded the maximum requests in this window
if (requestHistory.timestamps.length >= this.config.maxRequests) {
return false;
}
// Add the current request timestamp
requestHistory.timestamps.push(now);
// Return success
return true;
} catch (error) {
// Handle impossible error scenario where Date object becomes invalid
console.error('Rate limiter encountered critical error:', error);
throw new RateLimiterException('Rate limiter malfunction detected', error);
}
}
// Get the remaining requests for a given key
getRemainingRequests(key) {
if (!this.requestMap.has(key)) {
return this.config.maxRequests;
}
const remaining = this.config.maxRequests - this.requestMap.get(key).timestamps.length;
return Math.max(0, remaining);
}
}
// Custom exception class for rate limiter errors
class RateLimiterException extends Error {
constructor(message, originalError) {
super(message);
this.name = 'RateLimiterException';
this.originalError = originalError;
}
}
// Example usage
const limiter = new RateLimiter(new RateLimiterConfig(5, 60000));
(async () => {
for (let i = 0; i < 7; i++) {
const allowed = await limiter.isAllowed('user123');
console.log(`Request ${i + 1}: ${allowed ? 'allowed' : 'denied'}`);
}
})();
Code Review
1. Lines 3-10. RateLimiterConfig class adds zero value here. This is just storing two numbers. We're creating a class factory for what should be a plain object literal or just function parameters. This level of indirection is not needed.
2. Line 17. The WeakMap for internalStateWeakMap is declared but never used anywhere in the code. What was this supposed to track? Dead code that creates confusion about intent.
3. Lines 20-24. The validateKey method is overly defensive for a simple string check. We're throwing errors for edge cases that should probably just be handled gracefully. Also, the return true statement is pointless since we're not using the return value.
4. Line 27. The isAllowed method is marked async but never awaits anything. Why is this async? There's no I/O operation, no Promise here. This adds complexity and confusion for consumers of the API who will expect actual async behavior.
5. Lines 48-60. The try-catch block is catching errors related to Date.now() becoming invalid, which is impossible in JavaScript. This is phantom error handling for scenarios that cannot occur. The comment even admits this is 'impossible error scenario' so why catch it?
6. Lines 61-65. Creating a custom RateLimiterException class for what should just be a standard Error is over-engineering. Now consumers need to know about this custom exception type. Also the originalError field on line 67 suggests we're wrapping something, but we're mostly just adding layers.
7. Lines 73-76. The example usage with the IIFE is fine but the loop doesn't actually wait between requests. For a rate limiter demo, you'd want to show requests happening across time intervals to meaningfully demonstrate the window-based behavior. This just fires all requests immediately.