单例异步函数

4 阅读3分钟

单例异步函数(简单版)

/**
 * 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
    }
  }
}