Cache API Calls in JavaScript by Memoizing Promises

Written by aldrinvincent | Published 2022/03/14
Tech Story Tags: javascript | memoization | caching | web-performance | react | promises | web-caching | how-caching-works-with-js

TLDRCache API Calls in JavaScript by Memoizing Promises. A higher-order function that stores promises in a map with key as parameters of the function that make API call and if the same API is called with the same params, the result will be served from cache.via the TL;DR App

"There are only two hard problems in Computer Science: cache invalidation and naming things."

- Phil Karlton

Caching API calls is a common way to improve web app performance since caching reduces unnecessary and redundant API calls. Some common scenarios where unnecessary API calls might be happening are,

  1. Multiple modules in your app that make use of the same API endpoint at different points in time.
  2. The same API gets called again when the user re-visits the page.

If any of the repeated calls provide the same data every time, we could avoid them by caching and also save the round trips. It will reduce the load on the server and improve the performance of your web application at the same time.

How do you cache an API call?

The browser's HTTP cache should be the first choice for caching API calls because it is effective, supported in all browsers, and easy to implement. Caching of an API call in the browser's HTTP cache is controlled by its request headers and response headers.

Here is a good article on configuring HTTP cache on the client and server-side - https://web.dev/http-cache/

For a deeper understanding of HTTP caching, please check out MDN's HTTP caching article - https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching

In conclusion, the browser’s HTTP caching works in a combined effort of both client and server-side configurations. So we need to have control over both web application and web server for configuring request and response headers to make HTTP caching work as per our requirement. But sometimes we might not have control over server-side configurations or the server might not be sending all the required cache headers in the response.

In this article, let’s discuss one way to cache API calls in a web app using JavaScript if HTTP cache is not working for you.

Caching API calls by memoizing promises

In computing, memoization or memoisation is an optimization technique used primarily to speed up computer programs by storing the results of expensive function calls and returning the cached result when the same inputs occur again. This is the Wikipedia definition of memoization and we are going to use this technique to cache our API calls.

In the case of API calls, the result will be a promise and promises are javascript objects, so we can store it in a map data structure (calling it `cache`) with a unique key for each request. If the same API is called with the same key again, we can return the promise from the cache without fetching it again from the server.

To make our solution work, we will implement a higher-order function called memoizePromiseFn that takes a function that returns a promise as input and return memoized version of the function. We can use the output from memoizePromiseFn to make API calls and the results will be cached automatically with the key being the arguments array to the function. So if the function is called with the same arguments again, the result will be served from the cache. Here is our function that memoises a function that returns a promise,

const memoizePromiseFn = (fn) => {
    const cache = new Map(); 

    return (...args) => {
        const key = JSON.stringify(args);
    
        if (cache.has(key)) {
            return cache.get(key);
        }

        cache.set(key, fn(...args).catch((error) => {
            // Delete cache entry if API call fails
            cache.delete(key);
            return Promise.reject(error);
        }));

        return cache.get(key);
    };
};

The input for this function will be our service function that makes the API call to server and returns the result. For example,

function fetchTodo(id) {
  return fetch(`https://jsonplaceholder.typicode.com/todos/${id}`)
    .then((response) => response.json())
    .then((json) => json);
}

We can cache calls to the above function with id parameter as key using our promiseMemoize function. So if the fetchTodo function is called with the same id second time, response will be served from the cache without a roundtrip to the server. In case of an API call failure, the cached value will be cleared for the particular key.


async function getTodos() {
  
  // Here the higer order function is called
  let cachedFetchTodos = memoizePromiseFn(fetchTodo);

  let response1 = await cachedFetchTodos(1); // Call to server with id 1
  let response2 = await cachedFetchTodos(2); // Call to server with id 2
  let response3 = await cachedFetchTodos(1); // id is 1, will be served from cache
  let response4 = await cachedFetchTodos(3); // Call to server with id 3
  let response5 = await cachedFetchTodos(2); // id is 2, will be served from cache
  
  // Total number of calls - 3  
}

getTodos();

You can view demo in this codesandbox

Github repo - https://github.com/aldrinpvincent/memoize-promise-fn

If you are using react, place call to memoizePromiseFn ie,

let cachedFetchTodos = memoizePromiseFn(fetchTodo);

outside the react component. Otherwise, on every render, a different instance of memoized function will get created and the caching will not work as expected.

What are the issues with this Caching approach?

For memoization to work properly, the input function should be a pure function, because if the output of the function is different for the same input at different points in time, caching won’t work and you will run into stale data issues since the first result will be served every time.

Also, this is not a proper caching mechanism since the cache will get cleared on page reload. This mechanism may work for a single page application where routing happens on the client side without a full page reload and is applicable only for API calls whose response won’t change frequently.


Written by aldrinvincent |
Published by HackerNoon on 2022/03/14