Skip to main content

Cached fetch()

The fetch() is becoming de-facto standard from performing HTTP requests in browsers, being very widely used in place of the old XHR objects.

Sometimes though, when you're fetch()ing data, you don't want to actually hit the network each time this happens. It begs for a simple cache of results.

Since its API is very simple, we can easily wrap it in a bit of custom logic and conditionally fire off the actual request, depending of the cache state. In this particular case we'll treat the URL of the request as the cache key. In a more complex scenario, we need to hash out the URL, headers and body of the request into a distinct and predictable key that can be used for caching the response.

Here's the basic code that does it:

const store = new Map();

const cachedFetch = (url) =>
    ? new Promise((resolve) => {
        store.set(url, store.get(url).clone());
    : fetch(url).then((response) => store.set(url, response.clone()));

The snippet is very simplistic but illustrates the basic mechanism of the caching.

A couple of notes:

Usage: #

const fetchData = async () => {
  const response = await cachedFetch(

  return (await response.json())[0];
// Hits the network

// ...

// Doesn't actually hit the network, uses the response from the cache

Further improvements: #

Something similar is done in Remy Sharp's memfetch. The only thing that I don't like about it is that it overrides the global fetch() function. Basically this results in a global request interceptor which is forced upon the whole app code -- sometimes this is not desired. Providing a local wrapper is a bit more transparent and elegant in my view.

Profile picture

Andrei Glingeanu's notes and thoughts. You should follow him on Twitter, Instagram or contact via email. The stuff he loves to read can be found here on this site or on goodreads. Wanna vent or buy me a coffee?