-
Notifications
You must be signed in to change notification settings - Fork 0
createCacher()
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.
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()); // OKIf 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');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