All files / function/src memoize.ts

100% Statements 9/9
100% Branches 2/2
100% Functions 3/3
100% Lines 8/8

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77                                                                                                                        2x         2x 4x 4x 2x     2x 2x 2x      
/**
 * Create a new {@link Function} that returns the memoized results of a
 * {@link Function}. The key for the lookup cache is determined by the
 * `keyExtractor` {@link Function}.
 *
 * An optional {@link Map} instance can be provided to be used as cache. This
 * allows for example to prepopulate the cache, or to force recomputation of
 * some values by removing their corresponding keys. If no instance is provided
 * an empty {@link Map} will be created.
 *
 * @remarks
 * The memoized function should be a pure function, because else side effects
 * will only be executed once for each cached key.
 *
 * @example
 * Memoize an expensive function
 * ```ts
 * const expensiveFunction = (a, b) => a + b;
 * const keyExtractor = (a, b) => `${a}_${b}`;
 *
 * const memoizedFunction = memoize(expensiveFunction, keyExtractor);
 *
 * const result = memoizedFunction(2, 3);
 * console.log(result); // 5
 * ```
 *
 * @example
 * Provide an initial cache restored from database
 * ```ts
 * const restoredCacheFromDatabase = new Map([["a", 1], ["ab", 2]]);
 * const expensiveFunction = (s) => s.length;
 * const keyExtractor = (s) => s;
 *
 * const memoizedFunction = memoize(expensiveFunction, keyExtractor, restoredCacheFromDatabase);
 *
 * const result = memoizedFunction("ab");
 * console.log(result); // 2
 * ```
 *
 * @example
 * Clear cache after 5 seconds
 * ```ts
 * const cache = new Map();
 * const expensiveFunction = (s) => s.length;
 * const keyExtractor = (s) => s;
 *
 * const memoizedFunction = memoize(expensiveFunction, keyExtractor, cache);
 *
 * // ... fill the cache
 *
 * setTimeout(() => {
 *   cache.clear();
 * }, 5000);
 * ```
 *
 * @param fn - the {@link Function} to memoize
 * @param keyExtractor - the {@link Function} to extract the key for the cache
 * @param cache - an optional, possibly prefilled {@link Map} instance to use as cache
 * @returns the memoized result
 */
export const memoize = <TArgs extends unknown[], R>(
  fn: (...args: TArgs) => R,
  keyExtractor: (...args: TArgs) => unknown,
  cache = new Map<unknown, R>()
): ((...args: TArgs) => R) => {
  return (...args) => {
    const key = keyExtractor(...args);
    if (cache.has(key)) {
      return cache.get(key) as R;
    }
 
    const value = fn(...args);
    cache.set(key, value);
    return value;
  };
};