vue3+typescript实现http轮询方案

91 阅读2分钟

业务描述

用户点击“获取文件”按钮:

  1. 如果文件已生成、并成功上传到云盘,则直接下载文件;
  2. 如果文件正在生成中,则按钮回显为“生成中...”;
  3. 如果文件生成失败,则按钮回显为“重新获取”。

轮询逻辑

  1. 当用户点击“获取文件”按钮时,立即执行请求文件状态接口;
  2. 如果接口返回“文件生成中”,则开始轮询请求文件生成状态(每10s一次);
  3. 如果接口返回成功/失败、或者超时/页面销毁,则停止轮询。

实现思路

  1. getSubDownloadFile:定义文件状态请求接口;
  2. downlowndSubTask:递归轮询获取文件状态&下载链接
<a-button size="small" onClick={() => downlowndSubTask(row)}>
  {waiting ? '生成中...' : fail ? '重新获取' : '获取文件'}
</a-button>
/** 发起http请求:获取子任务下载文件 */
export const getSubDownloadFile = async (subTaskId: string) => {
  const { data } = await get<SubDownloadResult>('/download/task/sub_file', { subTaskId })
  return data || null
}

export interface SubDownloadResult {
  /** 子任务下载状态 */
  status: 'error' | 'waiting' | 'done'

  /** 信息 */
  message: string

  /** 下载链接 */
  url?: string
}
/** 轮询:递归循环获取下载链接 */
    const downlowndSubTask = async (task: DownloadSubTask, retry = 0) => {
      
      // 1,立即执行一次
      const subTaskId = task._id || ''
      const res = await getSubDownloadFile(task._id || '')

      // 2,遇到错误退出
      if (res?.status === 'error') {
        return $error(res.message) // 提示报错
      }
      
      // 3,获取到下载链接,退出
      const url = res?.url ?? ''
      if (url) {
        window.open(url)
        return
      }

      // 4,进入轮询
      const { timer: _timer } = execDelay(downlowndSubTask, [task, retry + 1], 10000)
      timer.value = _timer

    }

/**
 * 延迟执行某个函数
 *
 * @param func    要延迟执行的函数,可以是同步或异步
 * @param args    要执行函数的参数
 * @param delay   延迟毫秒数,默认0和直接调用一致
 */
export const execDelay = <T>(func: (...args: any[]) => Promise<T>, args: any[], delay = 0) => {
  let timer = 0
  const task: Promise<T> & { timer: number } = new Promise((resolve, reject) => {
    const execFn = () => {
      func
        .apply(null, [...args])
        .then((res) => {
          resolve(res)
        })
        .catch((e) => {
          reject(e)
        })
    }
    if (delay) {
      timer = setTimeout(() => execFn(), delay) as any
    } else {
      execFn()
    }
  }) as any

  task.timer = timer

  return task
}

延展思考

此处的轮询本质上属于一种异步循环。异步循环有两种情况:①异步串行 ②异步并行

  1. 实现异步循环时,先不要考虑循环控制流的问题,直接写一个执行一次的函数:doOne()

  2. 异步并行:可以用promise.all实现并行执行doOne()函数:

async doAll(list: any[]) => {
    Promise.all(list.map(l => doOne(l)))
}

  1. 异步串行:可以用递归的方式串行执行doOne()函数:
async doAll(i: number) => {

   // 立即执行一次
   doOne()

   // 设置退出条件
   if (i >= N) return

   // 递归执行
   doAll(i++)  // 如果执行时需要delay,可以改为execDelay(doAll(i++))

}

doAll(0)