105 lines
2.8 KiB
TypeScript
105 lines
2.8 KiB
TypeScript
/**
|
|
* Request Deduplication Utility
|
|
*
|
|
* Prevents duplicate API calls for the same resource within a time window.
|
|
* This significantly reduces server load and improves performance.
|
|
*/
|
|
|
|
interface PendingRequest<T> {
|
|
promise: Promise<T>;
|
|
timestamp: number;
|
|
}
|
|
|
|
class RequestDeduplicator {
|
|
private pendingRequests = new Map<string, PendingRequest<any>>();
|
|
private readonly DEFAULT_TTL = 5000; // 5 seconds default TTL
|
|
|
|
/**
|
|
* Execute a request with deduplication
|
|
*
|
|
* If a request with the same key is already pending and within TTL,
|
|
* the existing promise is returned instead of making a new request.
|
|
*
|
|
* @param key - Unique identifier for the request
|
|
* @param requestFn - Function that returns a promise for the request
|
|
* @param ttl - Time-to-live in milliseconds (default: 5000ms)
|
|
* @returns Promise that resolves with the request result
|
|
*/
|
|
async execute<T>(
|
|
key: string,
|
|
requestFn: () => Promise<T>,
|
|
ttl: number = this.DEFAULT_TTL
|
|
): Promise<T> {
|
|
// Check if there's a pending request
|
|
const pending = this.pendingRequests.get(key);
|
|
|
|
if (pending) {
|
|
const age = Date.now() - pending.timestamp;
|
|
|
|
// If request is still fresh, reuse it
|
|
if (age < ttl) {
|
|
console.log(`[RequestDeduplicator] Reusing pending request: ${key} (age: ${age}ms)`);
|
|
return pending.promise;
|
|
} else {
|
|
// Request is stale, remove it
|
|
console.log(`[RequestDeduplicator] Stale request removed: ${key} (age: ${age}ms)`);
|
|
this.pendingRequests.delete(key);
|
|
}
|
|
}
|
|
|
|
// Create new request
|
|
console.log(`[RequestDeduplicator] Creating new request: ${key}`);
|
|
const promise = requestFn()
|
|
.finally(() => {
|
|
// Clean up after request completes
|
|
this.pendingRequests.delete(key);
|
|
console.log(`[RequestDeduplicator] Request completed and cleaned up: ${key}`);
|
|
});
|
|
|
|
this.pendingRequests.set(key, {
|
|
promise,
|
|
timestamp: Date.now(),
|
|
});
|
|
|
|
return promise;
|
|
}
|
|
|
|
/**
|
|
* Cancel a pending request
|
|
*
|
|
* @param key - The request key to cancel
|
|
*/
|
|
cancel(key: string): void {
|
|
if (this.pendingRequests.has(key)) {
|
|
this.pendingRequests.delete(key);
|
|
console.log(`[RequestDeduplicator] Request cancelled: ${key}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clear all pending requests
|
|
*/
|
|
clear(): void {
|
|
const count = this.pendingRequests.size;
|
|
this.pendingRequests.clear();
|
|
console.log(`[RequestDeduplicator] Cleared ${count} pending requests`);
|
|
}
|
|
|
|
/**
|
|
* Get count of pending requests
|
|
*/
|
|
getPendingCount(): number {
|
|
return this.pendingRequests.size;
|
|
}
|
|
|
|
/**
|
|
* Get all pending request keys (for debugging)
|
|
*/
|
|
getPendingKeys(): string[] {
|
|
return Array.from(this.pendingRequests.keys());
|
|
}
|
|
}
|
|
|
|
// Singleton instance
|
|
export const requestDeduplicator = new RequestDeduplicator();
|