Smart caching for promises. Like memoization, but better.
const { cachifyPromise } = require('cachify-promise');
const cachedFetch = cachifyPromise(fetch);
await Promise.all([
cachedFetch('/api/users/1'),
cachedFetch('/api/users/1'),
cachedFetch('/api/users/1'),
cachedFetch('/api/users/42')
]); // Results in only 2 calls
const user = await cachedFetch('/api/users/42'); // From cache
npm install cachify-promise
- Promise deduplication
- Caches resolved values
- Ignores failed promises (unlike most memoization functions)
- Deletion of items
- Fully Typescript-ready
- Supports stale-while-revalidate caching policy
- Cleans expired items from the cache periodically
- Customizable (time-to-live, custom cache storage, key generation)
- No dependencies
- Works in browsers (requires
Promise
andMap
to be available or polyfilled) - Works in Node.js (10 or above)
const cachedFn = cachifyPromise(fn, options);
fn
: A function returning a promiseoptions
ttl
: Time-to-live in milliseconds (defaults toNumber.MAX_VALUE
). Set to0
to disable caching of resolved values.staleWhileRevalidate
: Enable 'stale-while-revalidate' policy (defaults tofalse
)cacheMap
: Cache instance, must implementhas
,get
,set
,delete
. Defaults tonew Map()
.cacheKeyFn
: Function for generating cache keys, must return strings.cleanupInterval
: Time in milliseconds that determines the interval at which a cleanup job is run. This job clears any expired cache items. Defaults to 10000 ms.statsFn
: Callback function to receive stats. Will be called on each update with an object containinghitPromise
,hitValue
,miss
andput
values.
- Returns a function with the same signature as
fn
.
const { cachifyPromise } = require('cachify-promise');
const cachedFetch = cachifyPromise(fetch, {
ttl: 3600 * 1000, // one hour
cacheKeyFn: (url, options) => `${options.method} ${url}`,
cacheMap: new Map(),
staleWhileRevalidate: true,
statsFn: stats => console.log('Cache statistics:', stats)
});
When performing expensive or time-consuming asynchronous tasks, it is often desirable to deduplicate calls while they are being done.
Imagine the fetchUser
function will make a HTTP call to fetch information about a user.
Naively, the following code will result in 2 HTTP calls being made:
// In UI component 1
const userPromise1 = fetchUser({ id: 1 });
// --> triggers HTTP call
// At the same time, in UI component 2
const userPromise2 = fetchUser({ id: 1 });
// --> triggers HTTP call
By wrapping the fetchUser
function with cachifyPromise
, only a single call will be made at the same time:
const cachedFetchUser = cachifyPromise(fetchUser);
// In UI component 1
const userPromise1 = cachedFetchUser({ id: 1 });
// --> triggers HTTP call
// At the same time, in UI component 2
const userPromise2 = cachedFetchUser({ id: 1 });
// --> returns previous promise
By default, resolved values will be cached for for a long time (Number.MAX_VALUE
milliseconds).
When a promise rejects, this will not be stored.
You can customize the time-to-live using the ttl
option (see Usage).
To disable caching of resolved values altogether, set ttl
to 0
.
The cache key is determined by running JSON.stringify
over the argument array passed to the function. You can provide your own key-generating function with the cacheKeyFn
option (see Usage).
When there are items in the cache, a periodic cleanup job is run to clean any expired items in the cache. The interval at which this job is run may be controlled with the cleanupInterval
option.
NOTE: cleanup is not run when the staleWhileRevalidate
policy is active
You can delete entries from the cache by invoking the .delete()
function. This function takes the same arguments as a regular invocation.
const cachedFetchUser = cachifyPromise(fetchUser);
// Invoke and store in cache
await cachedFetchedUser({ id: 1 });
// Removes user 1 from the cache
cachedFetchedUser.delete({ id: 1 });
Sometimes, it is acceptable to return a stale ('old') value when a cache item is past its time-to-live. In the meantime, a fresh value is being fetched in the background.
const cachedFetchUser = cachifyPromise(fetchUser, {
staleWhileRevalidate: true,
ttl: 10000
});
// In UI component
const userPromise1 = cachedFetchUser({ id: 1 });
// --> triggers HTTP call
// <HTTP call finishes>
await delay(10001);
const userPromise2 = cachedFetchUser({ id: 1 });
// --> resolves with cached user, AND triggers HTTP call in the background
// another 0.0001 seconds later
const userPromise3 = cachedFetchUser({ id: 1 });
// --> resolves with cached user, will NOT trigger HTTP call since one is already in progress
// <HTTP call finishes>
const userPromise4 = cachedFetchUser({ id: 1 });
// --> new user data!
When a statsFn
function is provided (see Usage), that function will be invoked each time a cache interaction takes place. The object passed as a parameter to that function will contain:
hitPromise
: Cache hits on pending promiseshitValue
: Cache hits on stored valuesmiss
: Cache missesput
: Cache puts
The number of cache accesses may be computed with:
access = stat.hitPromise + stat.hitValue + stat.miss;
As such, the hit and miss ratios may be calculated with:
hitRatio = (stat.hitPromise + stat.hitValue) / access;
missRatio = stat.miss / access;