All files / utils asyncMemoize.ts

100% Statements 16/16
100% Branches 10/10
100% Functions 4/4
100% Lines 14/14

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                                                                            14x 5x   5x 18x 18x 18x 18x       6x   12x 12x 12x       12x   5x   5x    
export interface AsyncMemoizeOptions<
  T extends (...args: any[]) => Promise<any>,
> {
  /**
   * 缓存键。
   *
   * @default arg0 => arg0
   */
  cacheKey?: (...args: Parameters<T>) => any
 
  /**
   * 缓存时效(毫秒)。
   *
   * @default 0
   */
  cacheTTL?: number
}
 
export type AsyncMemoizeCacheMap = Map<
  string,
  {
    value: any
    expiredAt: number
  }
>
 
/**
 * 异步函数执行缓存。
 *
 * @param asyncFn 异步函数
 * @param options 选项
 */
export function asyncMemoize<T extends (...args: any[]) => Promise<any>>(
  asyncFn: T,
  options: AsyncMemoizeOptions<T> = {},
): T & {
  cache: AsyncMemoizeCacheMap
} {
  const { cacheKey = (...args: any[]) => args[0], cacheTTL } = options
  const cache: AsyncMemoizeCacheMap = new Map()
 
  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
  }
  call.cache = cache
 
  return call as any
}