Why does my retry + cache logic sometimes leak memory in a long-lived SPA?

MechaPrime
Tue, 28 Apr 2026 07:00:12 +0000

Hey folks, I’m working on a pixel-art editor SPA and I’m trying to add network retry plus response caching for autosave, but after leaving the tab open all day the memory climbs and the UI starts stuttering.

const inflight = new Map();
const cache = new Map();

export function fetchWithRetry(url, opts = {}, retries = 3) {
  if (cache.has(url)) return Promise.resolve(cache.get(url));
  if (inflight.has(url)) return inflight.get(url);

  const ac = new AbortController();
  const p = fetch(url, { ...opts, signal: ac.signal })
    .then(r => r.json())
    .then(data => {
      cache.set(url, data);
      return data;
    })
    .catch(err => {
      if (retries > 0) return fetchWithRetry(url, opts, retries - 1);
      throw err;
    });

  inflight.set(url, p);
  return p;
}

What’s the cleanest way to structure this so retries don’t leave inflight/cache entries or AbortControllers hanging around (and ideally avoid thundering-herd refetches)?

MechaPrime