跳转到内容

创建可过期的 Promise 包装器

可用于分页请求等场景,通过 createExpirablePromise 包装器可以确保只应用最新的请求结果

使用示例

ts
import { createExpirablePromise } from './code'

/**
 * 模拟一个 API 请求
 */
const getListAPI = () => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve([1, 2, 3])
    }, Math.random() * 1000)
  })
}

// 通过 createExpirablePromise 创建一个可过期的 Promise
const expirableFetch = createExpirablePromise(getListAPI)

// 请求分页函数
const getList = async () => {
  const { result, expired } = await expirableFetch()
  // 如果过期了,则不处理
  if (expired) return
  console.log(result)
}

源码

ts
/**
 * 创建一个可过期的 Promise 包装器
 *
 * 该函数接收一个返回 Promise 的函数,并返回一个新的函数。
 * 新函数在被多次调用时,会使之前未完成的 Promise 过期,确保只处理最新的请求结果。
 *
 * @example
 * const expirableFetch = createExpirablePromise(fetchData);
 * const result1 = expirableFetch('/api/data1');
 * const result2 = expirableFetch('/api/data2');
 * // result1 的 expired 将会是 true,因为 result2 后调用
 * // 只有 result2 的 result 会被使用
 */
export function createExpirablePromise<P extends any[] = any[], R = any>(
  fn: (...args: P) => Promise<R>,
): () => Promise<{ result: R; expired: boolean }> {
  let setExpired: (() => void) | undefined

  return async (...args: P) => {
    if (setExpired) {
      // 设置上一个 Promise 已过期
      setExpired()
    }

    let expired = false
    setExpired = () => {
      expired = true
    }

    try {
      const result = await fn(...args)

      // 清理引用避免内存泄漏
      setExpired = void 0

      return {
        result,
        expired,
      }
    } catch (error) {
      // 发生错误时也清理引用
      setExpired = void 0
      throw error
    }
  }
}