type Cache < T > = Record < string , { exp : number | null ; value : T }>;
function memoize < TArgs extends any [] , TResult > (
func : ( ... args : TArgs ) => TResult ,
keyFunc : ( ( ... args : TArgs ) => string ) | null ,
return function callWithMemo ( ... args : any ) : TResult {
const key = keyFunc ? keyFunc ( ... args ) : JSON . stringify ( { args } );
const existing = cache [ key ];
if ( existing !== undefined ) {
if ( existing . exp > new Date () . getTime ()) {
const result = func ( ... args );
exp : ttl ? new Date () . getTime () + ttl : null ,
export interface MemoOptions < TArgs extends any []> {
key ?: ( ... args : TArgs ) => string ;
* Creates a memoized function. The returned function will only
* execute the source function when no value has previously been
* computed. If a ttl (milliseconds) is given previously computed
* values will be checked for expiration before being returned.
* const calls: number[] = []
* const fib = memo((x: number) => {
* return x < 2 ? x : fib(x - 1) + fib(x - 2)
* fib(10) // 55 (calls === [10])
* fib(11) // 89 (calls === [10, 11])
export function memo < TArgs extends any [] , TResult > (
func : ( ... args : TArgs ) => TResult ,
options : MemoOptions < TArgs > = {},
) : ( ... args : TArgs ) => TResult {
return memoize ({} , func , options . key ?? null , options . ttl ?? null );