Skip to content

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

typescript
client.dedup()
typescript
// 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 data in the key), but the safest practice is to call .dedup() only on clients used exclusively for reads.

Combining with other features

typescript
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')

Released under the MIT License.