#630 Dedupe In-flight Requests

medium
javascript
promises
async

A page often asks for the same data from several places at once. Three components mount, each calls getUser(42), and the browser sends three identical requests. A common fix is to share the request while it is still running: the first caller starts it, and anyone who asks for the same thing before it finishes gets the same promise back.

Write a function dedupe(fn, getKey?) that wraps an async function and returns a new function with the same arguments.

  • While a call for a given key is still pending, every new call with that key returns the same promise and does not call fn again.
  • Calls with different keys run independently.
  • Once the call settles (resolves or rejects), its key is forgotten. The next call runs fn again. This is not a cache of results, only of calls that are still running.
  • If the call rejects, every caller waiting on it sees that same error, and the next call retries.
  • If fn throws synchronously, the returned function should return a rejected promise instead of throwing, and nothing should be remembered.
  • By default the key is JSON.stringify(args), so { tag: "css" } and another { tag: "css" } count as the same call. When getKey is passed, use it to build the key from the arguments instead.
  • Each function returned by dedupe keeps its own record of pending calls.
const getUser = dedupe((id: number) => fetch(`/api/users/${id}`).then((r) => r.json()));

const a = getUser(42);
const b = getUser(42); // no new request, a === b
const c = getUser(7);  // different key, a new request

await a;
getUser(42);           // the first call finished, so this sends a fresh request

This pattern is sometimes called request coalescing or "single flight".