简单的接口缓存机制,避免了重复请求,同时支持缓存过期时间。
简单的接口缓存机制,避免了重复请求,同时支持缓存过期时间。
const CACHE_LIFETIME = 30
interface ApiCacheOptions {
/** 缓存时长(秒) */
cacheLifetime?: number
}
type CacheStatus = 'notStarted' | 'loading' | 'finished' | 'error'
interface CacheItem<T = any> {
status: CacheStatus
result: T | null
requestList: ((res: T | Error) => void)[]
timer?: NodeJS.Timeout
}
const resultCache = new Map<string, CacheItem>()
export async function apiCache<T>(
apiKey: string,
func: () => Promise<T>,
options?: ApiCacheOptions
): Promise<T> {
const cacheLifetime = options?.cacheLifetime ?? CACHE_LIFETIME
const item = getItem<T>(apiKey)
if (item.status === 'finished') {
return item.result as T
}
if (item.status === 'loading') {
return new Promise<T>((resolve, reject) => {
addSubscriber(apiKey, (res) => {
if (res instanceof Error) reject(res)
else resolve(res)
})
})
}
try {
item.status = 'loading'
item.result = await func()
item.status = 'finished'
// 设置缓存过期
item.timer = setTimeout(() => {
removeItem(apiKey)
}, cacheLifetime * 1000)
onAccessTokenFetched(apiKey, item.result)
return item.result
} catch (error) {
item.status = 'error'
onAccessTokenFetched(apiKey, error as Error) // 传递错误给订阅者
throw error
}
}
function getItem<T>(key: string): CacheItem<T> {
if (!resultCache.has(key)) {
resultCache.set(key, { status: 'notStarted', result: null, requestList: [] })
}
return resultCache.get(key) as CacheItem<T>
}
function removeItem(key: string) {
if (resultCache.has(key)) {
const item = resultCache.get(key)
if (item?.timer) clearTimeout(item.timer) // 清理定时器
resultCache.delete(key) // 彻底删除,防止内存泄漏
}
}
function addSubscriber<T>(key: string, callback: (res: T | Error) => void) {
const item = getItem<T>(key)
item.requestList.push(callback)
}
function onAccessTokenFetched<T>(key: string, result: T | Error) {
const item = getItem<T>(key)
item.requestList.forEach((callback) => callback(result))
item.requestList = [] // 清空请求列表
}