Skip to content

memoize

Edit

Memoizes successful synchronous results by receiver and arguments.

memoize.ts
type CacheNode = {
primitive: Map<unknown, CacheNode>;
object: WeakMap<object, CacheNode>;
hasValue: boolean;
value?: unknown;
};
export type MemoizeControls = {
readonly clear: () => void;
};
export type MemoizedFunction<
Arguments extends unknown[],
Return,
This = unknown,
> = ((this: This, ...args: Arguments) => Return) & MemoizeControls;
type AnyFunction = (this: never, ...args: never[]) => unknown;
type MemoizeGuard<Function extends AnyFunction> =
"clear" extends keyof Function ? never : [];
const createNode = (): CacheNode => ({
primitive: new Map(),
object: new WeakMap(),
hasValue: false,
});
const childFor = (node: CacheNode, key: unknown): CacheNode => {
const isObject = (typeof key === "object" && key !== null) || typeof key === "function";
const cache = isObject ? node.object : node.primitive;
let child = cache.get(key as never);
if (!child) {
child = createNode();
cache.set(key as never, child);
}
return child;
};
/** Memoizes successful results using the receiver and every argument as cache keys. */
export const memoize = <Function extends AnyFunction>(
fn: Function,
..._guard: MemoizeGuard<Function>
): Function & MemoizeControls => {
if ("clear" in fn) {
throw new TypeError('fn must not define a "clear" property');
}
let root = createNode();
const clear = () => {
root = createNode();
};
return new Proxy(fn, {
apply(target, receiver, args) {
let node = childFor(root, receiver);
for (const argument of args) node = childFor(node, argument);
if (node.hasValue) return node.value;
const value = Reflect.apply(target, receiver, args);
node.value = value;
node.hasValue = true;
return value;
},
get(target, property, receiver) {
return property === "clear"
? clear
: Reflect.get(target, property, receiver);
},
has(target, property) {
return property === "clear" || Reflect.has(target, property);
},
set(target, property, value, receiver) {
return property === "clear"
? false
: Reflect.set(target, property, value, receiver);
},
}) as Function & MemoizeControls;
};
Terminal
wget -O src/lib/memoize.ts https://raw.githubusercontent.com/jrTilak/lazykit/HEAD/registry/functions/memoize.ts
memoize.example.ts
import { memoize } from "./memoize";
const parse = memoize((source: string) => JSON.parse(source));
const value = parse('{"ready":true}');
parse.clear();
  • fn ((...args) => Return) — Function whose successful results are cached.
  • The invocation receiver and object arguments use reference identity; primitive keys use Map equality.
  • The returned function provides clear().
  • Properties on a callable object remain available through the wrapper.
  • A callable that already defines clear is rejected to avoid a control-name collision.