$ snippet memoize-lru
Memoize a function with an LRU cache
Cache expensive results by argument, cap the cache size, evict least-recently-used entries, and support async functions.
/**
* memoize(fn, { max: 100, key: (...args) => string })
* Works for sync and async fns (promises are cached; rejected ones are evicted).
*/
function memoize(fn, { max = 100, key = (...args) => JSON.stringify(args) } = {}) {
const cache = new Map(); // Map preserves insertion order → cheap LRU
const memoized = function (...args) {
const k = key(...args);
if (cache.has(k)) {
const v = cache.get(k);
cache.delete(k); cache.set(k, v); // move to most-recent
return v;
}
const result = fn.apply(this, args);
cache.set(k, result);
if (result instanceof Promise) result.catch(() => cache.delete(k));
if (cache.size > max) cache.delete(cache.keys().next().value); // evict oldest
return result;
};
memoized.clear = () => cache.clear();
memoized.cache = cache;
return memoized;
}How it works
A Map keeps keys in insertion order, so "least recently used" is simply the first key — delete-and-reinsert on every hit moves an entry to the end. Caching the promise itself (not the resolved value) means ten simultaneous calls with the same arguments trigger one request, and the rejection handler stops a failed call from being cached forever.
Gotchas
- The default key is
JSON.stringify(args): fine for primitives and small objects, wrong for functions, class instances or huge arguments. Pass a customkeythat picks out the identifying fields. - Memoization assumes the function is pure. Anything depending on time, randomness or external state will return stale results.
- Add a TTL if the underlying data changes — store
{ value, expires }and check on read, like the localStorage snippet.
Ad slot