All files / utils asyncMemoize.ts

100% Statements 15/15
100% Branches 10/10
100% Functions 4/4
100% Lines 13/13

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                                                        11x 4x               4x 15x 15x 15x 15x       5x   10x 10x 10x       10x     4x    
export interface AsyncMemoizeOptions<
  T extends (...args: any[]) => Promise<any>,
> {
  /**
   * 缓存键。
   *
   * @default arg0 => arg0
   */
  cacheKey?: (...args: Parameters<T>) => any
 
  /**
   * 缓存时效(毫秒)。
   *
   * @default 0
   */
  cacheTTL?: number
}
 
/**
 * 异步函数执行缓存。
 *
 * @param asyncFn 异步函数
 * @param options 选项
 */
export function asyncMemoize<T extends (...args: any[]) => Promise<any>>(
  asyncFn: T,
  options: AsyncMemoizeOptions<T> = {},
): T {
  const { cacheKey = (...args: any[]) => args[0], cacheTTL } = options
  const cache = new Map<
    string,
    {
      value: any
      expiredAt: number
    }
  >()
 
  const call = (...args: any[]) => {
    const _cacheKey = cacheKey(...args)
    const cacheValue = cache.get(_cacheKey)
    const currentMs = Date.now()
    if (
      cacheValue &&
      (cacheValue.expiredAt ? currentMs < cacheValue.expiredAt : true)
    ) {
      return cacheValue.value
    }
    const cacheValueNew = asyncFn(...args)
    cacheValueNew.catch(() => cache.delete(_cacheKey))
    cache.set(_cacheKey, {
      value: cacheValueNew,
      expiredAt: cacheTTL ? currentMs + cacheTTL : 0,
    })
    return cacheValueNew
  }
 
  return call as any
}