@chriscdn/memoize
v4.0.0
Published
Memoize a synchronous or asynchronous function.
Readme
@chriscdn/memoize
Memoize synchronous and asynchronous functions with an in-memory LRU cache.
Installing
Using npm:
npm install @chriscdn/memoizeUpgrading from v3 to v4
Breaking Change: As of v4, the shouldCache and ttl callbacks now accept an object containing args, value, and key. The refreshWhen callback now accepts an object containing args, value, key, and ttl.
Upgrading from v2 to v3
Breaking Change: As of v3, null return values are now cached.
Usage
import { Memoize, MemoizeAsync } from "@chriscdn/memoize";Memoize is used to memoize synchronous functions. MemoizeAsync provides the same caching behavior for asynchronous functions, ensuring that concurrent calls with the same cache key only execute the underlying function once.
The cache is backed by quick-lru. Each call to Memoize or MemoizeAsync creates a new cache instance.
By default, the cache key is generated by calling JSON.stringify() on the function arguments. This behavior can be customized using the resolver option. Depending on your needs, you might consider a package such as canonicalize to create consistent cache keys regardless of object property order.
Functions that return undefined are not cached. Functions that return null are cached.
Example (Synchronous)
To memoize a function:
const _add = (x: number, y: number) => x + y;
const add = Memoize(_add);The add function has the same interface as _add:
const result = add(5, 7);
// 12You can also define the function in a single line:
const add = Memoize((x: number, y: number) => x + y);Example (Asynchronous)
Memoizing an asynchronous function is similar:
const _add = async (x: number, y: number) => x + y;
const add = MemoizeAsync(_add);
const result = await add(5, 7);
// 12MemoizeAsync also prevents concurrent calls with the same cache key from evaluating the underlying function more than once.
Options
Both Memoize and MemoizeAsync accept an options parameter to control cache behavior:
const add = Memoize(_add, options);Available options and their defaults:
const options = {
// The maximum number of items in the cache.
maxSize: 1000,
// The default maximum duration in milliseconds an item can remain in the cache.
// A value returned by ttl overrides this.
// undefined means that items do not expire due to time constraints.
maxAge: undefined,
// A synchronous function that determines whether the return value
// should be added to the cache.
shouldCache: ({
value,
key,
args,
}: {
value: Return;
key: string;
args: Args;
}) => true,
// A synchronous function that determines the cache duration in milliseconds.
//
// A positive number uses that duration.
// null or undefined uses the configured maxAge.
// 0 or a negative number does not cache the value.
ttl: ({ value, key, args }: { value: Return; key: string; args: Args }) =>
null,
// A synchronous function to generate a cache key.
resolver: (...args) => JSON.stringify(args),
};The shouldCache and ttl options receive an object containing the resolved value, cache key, and original function args.
The shouldCache option can be used to conditionally skip caching based on a resolved value.
The ttl option can be used to determine the cache duration based on the resolved value, cache key, and original function arguments.
The refreshWhen option is available only with MemoizeAsync:
const add = MemoizeAsync(_add, {
refreshWhen: ({ ttl, args, value, key }) => ttl < 10000,
});When refreshWhen returns true for a cached value, MemoizeAsync refreshes the value in the background while returning the existing cached value immediately. The callback receives the remaining ttl, the original args, the cached value, and the cache key.
Cache
The underlying quick-lru instance is accessible via the .cache property on the memoized function:
const add = Memoize(_add);
const result = add(5, 7);
console.log(add.cache.size === 1);
// trueThe memoized function also exposes convenience methods for cache management:
// Remove a specific entry by arguments
add.delete(5, 7);
// Remove all entries
add.clear();
// Check whether an entry exists
add.has(5, 7);
// Get the remaining TTL in milliseconds
add.expiresIn(5, 7);A value can also be added directly to the cache by passing the arguments array:
// make 1+1=3
add.set([1, 1], 3);Class Methods
Class methods can also be memoized, but this requires overriding the method within the constructor. Ensure you bind the method to the instance to maintain the correct context. For example:
class AddClass {
count: number = 0;
constructor() {
this.add = Memoize(this.add.bind(this));
}
add(x: number, y: number) {
this.count += 1;
return x + y;
}
}Each memoized method in each class instance maintains its own cache.
Tests
Run the tests using:
pnpm test