The Cache API

The Cache API stores Request/Response pairs in named caches under the global caches. Usable from windows as well as service workers, it is Baseline Widely available (since April 2018) and needs a secure context. It ignores Cache-Control: entries never expire until your code deletes them. cache.add(url) fetches and stores in one step and rejects on a non-2xx status, while cache.put(request, response) stores any response you hand it. The listing builds a cache-first loader on Fetch (AJAX with Fetch):

A cache-first JSON loaderJavaScript
async function cacheFirst(url, cacheName = 'api-v1') {
  const cache = await caches.open(cacheName);
  const hit = await cache.match(url);
  if (hit) return ['cache', await hit.json()];
  const response = await fetch(url);
  if (response.ok) await cache.put(url, response.clone());   // a body is readable only once
  return ['network', await response.json()];
}
for (const attempt of [1, 2]) {
  const [from, products] = await cacheFirst('/data/products.json');
  console.log(attempt, from, products.length);
}
Output
1 network 3
2 cache 3

match() treats another query string, method or Vary header as a different request unless told to ignore it (ignoreSearch and similar options). Version cache names, list them with caches.keys() and caches.delete() old ones on deploy; SW Offline Support builds offline support on this pattern inside a service worker.