找回密码
 立即注册
首页 业界区 安全 使用 TypeScript 的指数退避机制包装异步请求 ...

使用 TypeScript 的指数退避机制包装异步请求

篁瞑普 4 天前
使用 TypeScript 的指数退避机制包装异步请求

在进行网络请求时,很有可能会遇到某些临时失败,例如网络波动、请求超时、服务器端未响应等。面对这种情况,最好的做法往往是实施一种重试机制,而指数退避(Exponential Backoff) 是一种非常流行且有效的重试策略。它通过递增间隔时间来避免系统过度拥塞,提高成功执行的几率。
本文将基于 TypeScript 和 Axios 构造一个适用于任意 API 请求的通用 Promise 包装函数,并包含支持指数退避重试的功能。
什么是指数退避?

指数退避是通过延迟策略实现的一种算法,其核心是随着重试次数的增加,系统会按照展示增长的方式增加每次重试前等待的时间。公式如下:
  1. delay = baseDelay * (2 ^ attempt)
复制代码
假如 baseDelay 为 1000 毫秒,第一次重试后等待 1 秒,第二次重试等待 2 秒,第三次等待 4 秒……延迟的时间会迅速增大。
这种机制可以有效减少系统的负载压力,同时也可以应对一些临时性的问题,比如网络波动、瞬时连接错误等。
指数退避的代码实现

我们使用 axios 库作为 HTTP 客户端,通过包装一个请求函数,并在请求失败时实现可配置的重试机制。下面是完整版代码:
  1. /**
  2. * 包装一次请求,支持指数退避的自动重试。
  3. * @param requesrtFn 实际执行请求的函数,应该返回一个 Promise
  4. * @param maxRetries 最大重试次数,默认2次
  5. * @param baseDelay 基础延迟时间,单位毫秒,默认1000ms
  6. * @returns 请求成功时的响应数据
  7. * @throws 最后一次请求失败的错误
  8. */
  9. async function requestWithRetry<T>(
  10.   requestFn: () => Promise<T>,
  11.   maxRetries = 2,
  12.   baseDelay = 1000
  13. ): Promise<T> {
  14.   let lastError: any;
  15.   
  16.   // 循环次数:0(初始尝试) + maxRetries(重试次数)
  17.   for (let attempt = 0; attempt <= maxRetries; attempt++) {
  18.     try {
  19.       // 调用真正的请求函数,若成功则直接返回结果
  20.       return await requestFn();
  21.     } catch (err) {
  22.       // 捕获异常,记录下来以便在全部尝试结束后抛出
  23.       lastError = err;
  24.       
  25.       // 判断是否为网络错误
  26.       const isNetworkError = axios.isAxiosError(err) &&
  27.         (!err.response || // 没有收到服务器响应
  28.           err.code === 'ECONNABORTED' || // 超时
  29.           err.code === 'ECONNREFUSED'); // 连接被拒绝
  30.       
  31.           // 若是网络错误,且还有剩余重试次数,则执行指数退避
  32.       if (isNetworkError && attempt < maxRetries) {
  33.         // 计算本次等待时间: baseDelay * 2^attempt
  34.         // 第一次失败等待 1 s(baseDelay),第二次失败等待 2 s,第三次失败等待 4 s,以此类推
  35.         const delay = baseDelay * Math.pow(2, attempt);
  36.         console.log(
  37.           `[EverMemOS] Retry attempt ${attempt + 1}/${maxRetries} after ${delay}ms`)
  38.           ;
  39.         // 使用 Promise + setTimeout 实现异步等待
  40.         await new Promise(resolve => setTimeout(resolve, delay));
  41.         // 继续下一轮循环(再次调用 requestFn)
  42.         continue;
  43.       }
  44.       break;
  45.     }
  46.   }
  47.   // 所有尝试都失败了,抛出最后一次失误给调用方
  48.   throw lastError;
  49. }
复制代码
核心功能拆解

1. 指数退避的实现

指数退避的核心公式是 baseDelay * Math.pow(2, attempt)。代码中:

  • baseDelay 是每次重试的基础等待时间,初始值由调用者传递(默认为 1000 毫秒)。
  • 每次重试后,等待时间按指数增加,第一次重试等待 baseDelay 毫秒,第二次等待 baseDelay * 2 毫秒,第三次等待 baseDelay * 4 毫秒。
通过 Promise 与 setTimeout 的结合,实现异步等待:
  1. await new Promise(resolve => setTimeout(resolve, delay));
复制代码
从而使程序在每次重试之前等待 动态时间间隔
2. 网络错误的处理逻辑

在重试机制中我们需要区分可重试错误与不可重试错误。事实上,只有网络错误才适合执行重试,例如:

  • 超时(HTTP 状态码为 408 或 ECONNABORTED)。
  • 连接被拒绝(ECONNREFUSED)。
  • 服务器未响应(没有 response 返回)。
通过 Axios 的 isAxiosError 方法,代码对错误类型进行了详细判断,确保只有网络相关错误被捕获:
  1. const isNetworkError = axios.isAxiosError(err) &&
  2.   (!err.response || err.code === 'ECONNABORTED' || err.code === 'ECONNREFUSED');
复制代码
3. 最大重试次数的控制

为了防止无限重试引发性能问题,该实现使用了 maxRetries 参数以控制最大尝试次数。代码中明确约定了:

  • 初次尝试计为第 0 次。
  • 重试次数最多不超过 maxRetries。
这里控制重试次数:
  1. for (let attempt = 0; attempt <= maxRetries; attempt++) {
复制代码
在这个例子中:

  • 我们尝试从 https://api.example.com/data 获取数据。
  • 如果请求失败,最多会重试 3 次,每次等待时间依次为 1 秒、2 秒、4 秒
  • 如果所有尝试都失败,最终会抛出错误并记录到日志。
输出示例

假设一次请求出现错误,以下是输出:
  1. import axios from 'axios';
  2. const client = axios.create({
  3.   baseURL: 'https://api.example.com',
  4.   timeout: 5000
  5. });
  6. async function fetchData() {
  7.   try {
  8.     const response = await requestWithRetry(client, () => client.get('/data'), 3, 1000);
  9.     console.log('Data: ', response.data);
  10.   } catch (error) {
  11.     console.error('Failed to fetch data:', error);
  12.   }
  13. }
  14. fetchData();
复制代码
最适用的场景


  • 网络请求中容易由于瞬时错误失败,比如 API 接口短暂不可用。
  • 需要提高异步操作的鲁棒性,而不是因为一次失败就终止流程。
总结

使用 TypeScript + Axios,借助指数退避实现了自动重试的功能。该解决方案兼容性强、扩展性高,适用于任何异步任务场景。通过适当配置 maxRetries 与 baseDelay,不仅可以提升操作的容错性,还能够避免在高压力环境下对系统资源的过度消耗。
如果你需要灵活的异步请求重试机制,这将是一个理想的实现。

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册