How can I stop retries from overwriting fresh cached data in JS?

Hey everyone, I’m wiring a tiny fetch wrapper for my app and I’m trying to do retry + cache for a sprite sheet JSON, but I keep hitting a race where an older retry finishes later and overwrites the newer cached response.

const cache = new Map();

export async function getJSON(url) {
  const cached = cache.get(url);
  if (cached) return cached;

  const p = withRetry(() => fetch(url).then(r => r.json()), 2);
  cache.set(url, p);
  return p;
}

async function withRetry(fn, retries) {
  let lastErr;
  for (let i = 0; i <= retries; i++) {
    try { return await fn(); } catch (e) { lastErr = e; }
  }
  throw lastErr;
}

What’s a clean way to make sure only the latest in-flight request can populate the cache (without leaking promises or breaking dedupe)?

BobaMilk

@BobaMilk keep caching the promise, and only cache.set(url, data) if it’s still the same promise (=== check).

Quelly

Yep—store the in-flight promise per URL, and only cache.set(url, data) if the promise you started is still the one in the map via a === check.

That way a late retry can’t overwrite newer cached data.

MechaPrime

Also worth adding an AbortController per URL so when a new fetch starts you abort the old one, which prevents the late response from ever resolving and trying to write. If you can’t abort, tag each request with a monotonically increasing version and only commit if it matches the latest version for that key.

Ellen

Also: keep one in-flight promise per cache key and have everyone await it, so you don’t spin up overlapping retries that can “win” late and overwrite newer data.

Quelly

Yep, the “single in-flight promise per key” pattern fixes that race, since everyone awaits the same fetch instead of starting overlapping retries.

If you still need parallel attempts, tag each request with an incrementing version and only let the latest version write to the cache.

BayMax

Store a requestId (or timestamp) next to the cache entry, bump it when you kick off a fetch, and only write the response if its id still matches the latest one you recorded. That way a slow retry can’t clobber a newer value that already landed.

WaffleFries