Skip to content

createCacher()

Amphiluke edited this page Dec 1, 2025 · 3 revisions

Synopsis

The createCacher() API guards a user-defined async function by producing a thing “cacher”, an async wrapper that caches the results of the original function call. Repeated call attempts just return previously cached promise without relaunching the original function.

Usage example

import {createCacher} from 'async-aid';

// List of countries rarely changes and can be safely cached
const getCountries = createCacher(async () => {
  console.log('Fetching…');
  const response = await fetch('/geo-api/countries');
  return await response.json();
});

// First call. Logs 'Fetching…' and sends a request
const countries = await getCountries();

// Subsequent calls use cache and don’t log 'Fetching…'
console.assert(countries === await getCountries()); // OK

Rejection handling

If the original function throws an exception or produces a promise which eventually gets rejected, then a cacher automatically resets its internal cache thus allowing for a new call attempt. In other words, rejected promises do not stay in cache by default.

import {createCacher} from 'async-aid';

// Say, we have several mirrors providing Geo APIs
const getCountries = createCacher(async (baseURL) => {
  const response = await fetch(`${baseURL}/geo-api/countries`);
  return response.ok ?
    await response.json() :
    Promise.reject(`${baseURL} is unavailable`);
});

// Trying the first mirror (which appears to be unavailable)
const result1 = await getCountries('<faulty-url>').catch((reason) => reason);
console.assert(result1 === '<faulty-url> is unavailable');

// Retrying with the second mirror (which is working) is still possible
const result2 = await getCountries('<working-url>').catch((reason) => reason);
console.assert(Array.isArray(result2));

You may opt out of this behaviour by specifying the cacheRejection option when creating a cacher. In this case, a cacher is not selective about what the original function returns.

import {createCacher} from 'async-aid';

// Say, we have several mirrors providing Geo APIs
const getCountries = createCacher(async (baseURL) => {
  const response = await fetch(`${baseURL}/geo-api/countries`);
  return response.ok ?
    await response.json() :
    Promise.reject(`${baseURL} is unavailable`);
}, {
  cacheRejection: true,
});

// Trying the first mirror (which appears to be unavailable)
const result1 = await getCountries('<faulty-url>').catch((reason) => reason);
console.assert(result1 === '<faulty-url> is unavailable');

// Retrying with the second mirror has no effect, the cached rejection is returned
const result2 = await getCountries('<working-url>').catch((reason) => reason);
console.assert(result2 === '<faulty-url> is unavailable');

Maintaining multiple caches

Sometimes you may want your cacher to use different caches depending on arguments it is passed. Say, Geo API in our examples could return countries on the per-continent basis. In this case, our cacher should maintain several caches, one for each continent. This is where the concept of a key function comes into play.

In order to make a cacher maintain multiple independent caches, you need to provide it an additional option keyFn. This is a function which takes the same arguments as the original async function and returns a distinct key (cache identifier). A cacher uses this this key to differentiate which cache storage to retrieve. To make things clear, consider the following example.

import {createCacher} from 'async-aid';

// Get country list for a continent
const getCountriesByContinent = createCacher(async (continentCode) => {
  console.log(`Fetching for ${continentCode}…`);
  const response = await fetch(`/geo-api/countries?continent=${continentCode}`);
  return await response.json();
}, {
  // Use continent code as a distinct cache key
  keyFn: (continentCode) => continentCode,
});

// Logs 'Fetching for AF…' and sends a request
const africanCountries = await getCountriesByContinent('AF');

// Logs 'Fetching for OC…' and sends a request
const oceaniaCountries = await getCountriesByContinent('OC');

// Logs nothing and doesn’t send a request (cache for AF is used)
console.assert(africanCountries === await getCountriesByContinent('AF')); // OK

Clone this wiki locally