method/memorize.ts

/** @module Function */

import { isNum } from '@number/isNum'
import { hasOwn } from '@object/hasOwn'
import { isFunc } from './isFunc'
import { noOp } from './noOp'

/**
 * Creates a method to memorize passed in methods output
 * @example
 * memorize(myFunction, cacheKeyFunction)
 * @example
 * memorize(myFunction, cacheKeyFunction, 100)
 * @function
 * @param {Function} func - method to memorize output of
 * @param {Function} getCacheKey - gets the key to save cached output
 *
 * @return {Function} memorized function with cache
 */
export const memorize = (
  func: (...params: any[]) => any,
  getCacheKey: (...params: any[]) => any,
  limit = 1
): ((...params: any[]) => any) => {
  if (!isFunc(func) || (getCacheKey && !isFunc(getCacheKey))) {
    console.error('Error: Expected a function', func, getCacheKey)
    return noOp
  }

  let memorized = function () {
    const cache = (memorized as any).cache
    const key = getCacheKey ? getCacheKey.apply(this, arguments) : arguments[0]

    if (hasOwn(cache, key)) return cache[key]

    const result = func.apply(this, arguments)

    isNum(limit) && Object.keys(cache).length < limit
      ? (cache[key] = result)
      : ((memorized as any).cache = { [key]: result })

    return result
  }

  // @ts-ignore
  memorized.cache = {}
  // @ts-ignore
  memorized.destroy = () => {
    getCacheKey = undefined
    // @ts-ignore
    memorized.cache = undefined
    // @ts-ignore
    memorized.destroy = undefined
    memorized = undefined
  }

  return memorized
}