Request Deduplication
Request deduplication coalesces identical concurrent calls into a single network request. All callers receive the same resolved (or rejected) response — zero wasted bandwidth.
The problem
In a server-rendered or microservice environment, multiple parts of the code may request the same resource simultaneously — e.g. three components fetching /users/me during a single request lifecycle:
Without dedup: With dedup:
GET /users/me ──► network GET /users/me ──► network
GET /users/me ──► network GET /users/me ──┘ (shared)
GET /users/me ──► network GET /users/me ──┘ (shared)
3 network calls 1 network call ✓Usage
client.dedup()// Three simultaneous calls → one HTTP request, three callers get the same result
const [a, b, c] = await Promise.all([
client.get('/users/me'),
client.get('/users/me'),
client.get('/users/me'),
])
// a === b === c (same response object)How it works
Calls are keyed by METHOD:URL:params. While a request is in-flight, any new call with the same key reuses the existing Promise. Once the request settles (success or error), the entry is removed — the next call starts a fresh request.
Important limitations
Only use for idempotent requests
Deduplication is designed for GET and HEAD requests. Do not use it for POST, PUT, PATCH, or DELETE — those are not idempotent and should never be coalesced.
- POST/PUT/PATCH/DELETE are keyed differently (include
datain the key), but the safest practice is to call.dedup()only on clients used exclusively for reads.
Combining with other features
const resourceClient = HttpClientFactory.create('https://api.example.com')
resourceClient
.retry(3, new ExponentialJitterRetryStrategy(100, 5_000))
.dedup() // coalesce concurrent reads before retry/circuit-breaker overhead
const { data: user } = await resourceClient.get('/users/me')