NeahNew/lib/utils/request-deduplication.ts
2026-01-06 13:02:07 +01:00

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();