Skip to content

Commit

Permalink
feat(utils): 新增 asyncMemoize
Browse files Browse the repository at this point in the history
  • Loading branch information
fjc0k committed Jun 10, 2023
1 parent c16fe73 commit cd2cfca
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 0 deletions.
91 changes: 91 additions & 0 deletions src/utils/asyncMemoize.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { asyncMemoize } from './asyncMemoize'
import { wait } from './wait'

describe('asyncMemoize', () => {
test('ok', async () => {
let i = 0
const fn = async (id: number) => {
i++
return id
}
const fnMemoized = asyncMemoize(fn)

expect(await fnMemoized(2)).toBe(2)
expect(i).toBe(1)

expect(await fnMemoized(2)).toBe(2)
expect(i).toBe(1)

expect(await fnMemoized(3)).toBe(3)
expect(i).toBe(2)

expect(await fnMemoized(3)).toBe(3)
expect(i).toBe(2)

expect(await fnMemoized(3)).toBe(3)
expect(i).toBe(2)

expect(await fnMemoized(4)).toBe(4)
expect(i).toBe(3)
})

test('不缓存报错', async () => {
let i = 0
const fn = async (id: number) => {
i++
throw new Error(`${id}`)
}
const fnMemoized = asyncMemoize(fn)

await expect(fnMemoized(2)).rejects.toThrowError('2')
expect(i).toBe(1)

await expect(fnMemoized(2)).rejects.toThrowError('2')
expect(i).toBe(2)
})

test('支持 cacheKey', async () => {
let i = 0
const fn = async (id: number) => {
i++
return id
}
const fnMemoized = asyncMemoize(fn, {
cacheKey: id => (id <= 3 ? 2 : id),
})

expect(await fnMemoized(2)).toBe(2)
expect(i).toBe(1)

expect(await fnMemoized(3)).toBe(2)
expect(i).toBe(1)

expect(await fnMemoized(4)).toBe(4)
expect(i).toBe(2)

expect(await fnMemoized(5)).toBe(5)
expect(i).toBe(3)
})

test('支持 cacheTTL', async () => {
let i = 0
const fn = async (id: number) => {
i++
return id
}
const fnMemoized = asyncMemoize(fn, {
cacheTTL: 30,
})

expect(await fnMemoized(2)).toBe(2)
expect(i).toBe(1)

expect(await fnMemoized(2)).toBe(2)
expect(i).toBe(1)

await wait(50)

expect(await fnMemoized(2)).toBe(2)
expect(i).toBe(2)
})
})
58 changes: 58 additions & 0 deletions src/utils/asyncMemoize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
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
}
1 change: 1 addition & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export * from 'lodash-uni'
// @index(['./**/*.ts', '!./**/*.{test,perf}.*'], f => `export * from '${f.path}'`)
export * from './asRequiredDeep'
export * from './asyncLimit'
export * from './asyncMemoize'
export * from './base64'
export * from './bindEvent'
export * from './Calculator'
Expand Down

0 comments on commit cd2cfca

Please sign in to comment.