单例异步函数(简单版)
/**
* singletonAsync 函数说明
*
* 作用:
* 将一个异步函数包装成单例模式,确保同一时间只有一个异步调用在执行。
* 如果在异步函数执行期间再次调用,会返回正在执行的 Promise,而不是创建新的调用。
*
* 使用场景:
* - API 请求防重:防止用户快速点击导致重复请求
* - 资源初始化:确保初始化逻辑只执行一次(如获取配置、建立连接)
* - 数据加载:避免并发加载相同数据造成资源浪费
*
* 工作原理:
* 1. 使用 isRunning 标记函数是否正在执行
* 2. 使用 promise 缓存正在执行的 Promise 对象
* 3. 如果函数正在执行(isRunning = true),直接返回缓存的 promise
* 4. 如果函数未执行,则执行函数并标记 isRunning = true
* 5. 执行完成后重置状态(promise = null, isRunning = false)
* 6. 如果执行出错,同样重置状态并抛出错误
*
* 示例:
* ```typescript
* const fetchUserData = async (userId: string) => {
* const response = await fetch(`/api/users/${userId}`)
* return response.json()
* }
*
* const singletonFetchUserData = singletonAsync(fetchUserData)
*
* // 同时调用多次,只会发起一次请求
* singletonFetchUserData('123') // 发起请求
* singletonFetchUserData('123') // 返回第一次的 Promise,不会重复请求
* singletonFetchUserData('123') // 返回第一次的 Promise,不会重复请求
* ```
*
* 注意事项:
* - 此函数不会缓存结果,只是防止并发执行
* - 每次执行完成后,下次调用会重新执行函数
* - 如果需要缓存结果,需要配合其他缓存机制使用
*/
export default function singletonAsync(fn: (...args: any[]) => Promise<any>) {
let promise: Promise<any> | null = null
let isRunning = false
return async function internalSingletonAsync(...args: any[]) {
if (isRunning) {
return promise
}
isRunning = true
try {
promise = fn(...args)
const result = await promise
promise = null
isRunning = false
return result
} catch (e) {
promise = null
isRunning = false
throw e
}
}
}
单例异步函数(缓存版)
/**
* singletonAsyncWithCache 函数说明
*
* 作用:
* 将一个异步函数包装成带缓存的单例模式,不仅防止并发执行,还会缓存执行结果。
* 后续调用会直接返回缓存的结果,而不会重新执行函数。
*
* 使用场景:
* - 配置加载:应用启动时加载配置,后续直接使用缓存
* - 用户信息获取:登录后获取用户信息,整个会话期间复用
* - 静态资源加载:加载字典、枚举等不变的数据
* - 权限信息:获取用户权限列表并缓存
*
* 工作原理:
* 1. 使用 cachedResult 缓存执行结果
* 2. 使用 isCached 标记是否已有缓存
* 3. 如果已有缓存(isCached = true),直接返回 cachedResult
* 4. 如果正在执行(isRunning = true),返回正在执行的 promise
* 5. 如果未执行且无缓存,执行函数并缓存结果
* 6. 执行成功后设置 isCached = true,缓存结果
* 7. 执行失败则不缓存,允许下次重试
*
* 与 singletonAsync 的区别:
* - singletonAsync:只防止并发,不缓存结果,每次执行完都会重新调用
* - singletonAsyncWithCache:防止并发 + 缓存结果,只执行一次,后续返回缓存
*
* 示例:
* ```typescript
* const fetchAppConfig = async () => {
* const response = await fetch('/api/config')
* return response.json()
* }
*
* const getAppConfig = singletonAsyncWithCache(fetchAppConfig)
*
* // 第一次调用,发起请求
* const config1 = await getAppConfig() // 发起请求
*
* // 后续调用,直接返回缓存结果
* const config2 = await getAppConfig() // 返回缓存,不发起请求
* const config3 = await getAppConfig() // 返回缓存,不发起请求
* ```
*
* 注意事项:
* - 缓存是永久的,除非重新创建函数实例
* - 适用于不会变化的数据,如果数据会更新,需要手动清除缓存或使用其他方案
* - 如果需要支持缓存过期、手动清除等功能,可以扩展此函数
*/
export function singletonAsyncWithCache<T extends (...args: any[]) => Promise<any>>(
fn: T
): (...args: Parameters<T>) => Promise<Awaited<ReturnType<T>>> {
let promise: Promise<any> | null = null
let isRunning = false
let isCached = false
let cachedResult: any = null
return async function internalSingletonAsyncWithCache(...args: Parameters<T>) {
// 如果已有缓存,直接返回缓存结果
if (isCached) {
return cachedResult
}
// 如果正在执行,返回正在执行的 Promise
if (isRunning) {
return promise
}
isRunning = true
try {
promise = fn(...args)
const result = await promise
// 缓存结果
cachedResult = result
isCached = true
promise = null
isRunning = false
return result
} catch (e) {
// 执行失败不缓存,允许下次重试
promise = null
isRunning = false
throw e
}
}
}