UniApp中HTTP请求封装:实现503自动重试的优雅方案

前端 潘老师 1周前 (04-15) 21 ℃ (0) 扫码查看

UniApp开发与后端API进行交互时,后端服务可能会因各种原因(如维护、过载等)返回503状态码,这意味着服务临时不可用 。为了提升应用的健壮性和用户体验,避免手动编写繁琐的重试逻辑,我们可以使用一个专为UniApp框架设计的增强型HTTP请求封装模块。该模块基于uni.request,用TypeScript编写,下面来详细了解一下。

一、模块功能特性介绍

(一)503自动重试

当调用后端接口遇到503 Service Unavailable错误时,该模块无需手动干预,会自动根据预设或自定义的规则进行重试。这一功能避免了在每个请求中都去编写重试代码的麻烦,极大地简化了开发流程。

(二)灵活的配置选项

此模块支持全局设置默认的重试次数和延迟时间,同时也允许在单次请求时覆盖这些默认配置。开发者可以根据不同的业务场景,灵活调整重试策略。

(三)Promise化处理

模块完全基于Promise,这使得它能完美支持async/await语法。使用这种方式编写异步代码,逻辑更加清晰,可读性更高,也方便进行错误处理。

(四)辅助函数便捷调用

模块提供了httpGethttpPosthttpPuthttpDelete等常用方法的快捷方式。通过这些辅助函数,开发者可以更简便地发起不同类型的HTTP请求,减少重复代码的编写。

(五)结构化错误处理

当请求失败时,模块会reject一个包含详细信息的HttpRequestError对象。这个对象包含错误消息、原始错误、状态码以及是否为网络错误等信息,有助于开发者进行精细化的错误处理,快速定位和解决问题。

(六)可控的错误提示

请求失败时,模块默认会通过uni.showToast弹出提示,告知用户请求失败的信息。不过,如果开发者有自定义UI反馈的需求,可以通过设置hideErrorToast: true选项来禁用默认提示。

(七)类型安全保障

借助TypeScript的泛型<T>,开发者可以指定期望的响应数据类型。这样在编译阶段就能进行更严格的类型检查,有效减少因类型不匹配导致的错误,提高代码的稳定性。

(八)独立纯净的设计

该模块不依赖外部状态管理库(如Pinia/Vuex),具有很强的独立性。这使得它能轻松集成到任何UniApp项目中,不会给项目引入额外的依赖负担。

二、适用场景分析

这个HTTP请求封装模块适用于多种场景:

  • 任何涉及与后端API交互的UniApp项目,都可以使用该模块来简化网络请求的处理过程。
  • 对于那些希望提高应用对后端临时故障容错能力的项目,该模块的503自动重试功能能有效提升应用的稳定性。
  • 寻求更简洁、统一的网络请求处理方式的开发者,也可以通过使用这个模块,优化项目中的请求代码结构。

三、使用方法详解

使用该模块时,先将request.ts(假设模块文件名为request.ts)放置在项目的工具(utils)目录下。然后,在需要发起网络请求的页面或组件中,导入http或其辅助函数(如httpGethttpPost等)进行调用即可。下面是一个简单的示例:

// 示例:
import { http } from '@/utils/request'; // 假设你放在 utils 目录下

async function fetchData() {
  try {
    const data = await http.get<MyDataType>('/api/data');
    console.log(data);
  } catch (error) {
    console.error('请求失败:', error);
    // 处理错误...
  }
}

四、代码解析

(一)配置常量

// --- 配置常量 ---
const DEFAULT_MAX_RETRIES = 2; // 默认最大重试次数 (总尝试次数 = 1 + 重试次数)
const DEFAULT_RETRY_DELAY = 1000; // 默认两次重试之间的延迟时间 (毫秒)

这里定义了两个常量,DEFAULT_MAX_RETRIES表示默认的最大重试次数,总尝试次数是1(首次请求)加上重试次数;DEFAULT_RETRY_DELAY表示默认的两次重试之间的延迟时间,单位为毫秒。

(二)TypeScript接口定义

// --- TypeScript 接口定义 ---

/**
 * 扩展 UniApp 的 RequestOptions,增加自定义的重试配置
 */
interface RetryRequestOptions extends UniApp.RequestOptions {
  /** 针对 503 错误的最大重试次数 (默认: 2) */
  retries?: number;
  /** 每次重试之间的延迟时间,单位毫秒 (默认: 1000) */
  retryDelay?: number;
  /** 设置为 true 可隐藏自动弹出的错误提示 Toast (默认: false) */
  hideErrorToast?: boolean;
}

/**
 * 定义一个结构化的 HTTP 请求错误对象,用于 Promise 的 reject
 */
interface HttpRequestError {
  /** 错误消息文本 */
  message: string;
  /** uni-app 返回的原始错误对象或请求失败的响应对象 */
  originalError: UniApp.GeneralCallbackResult | UniApp.RequestSuccessCallbackResult;
  /** 标志是否为网络连接错误 (即 fail 回调触发) */
  isNetworkError?: boolean;
  /** HTTP 状态码 (如果是非 2xx 的成功响应) */
  statusCode?: number;
}

RetryRequestOptions接口扩展了UniApp原有的RequestOptions,增加了retries(最大重试次数)、retryDelay(重试延迟时间)和hideErrorToast(是否隐藏错误提示Toast)三个自定义配置项。HttpRequestError接口则定义了请求失败时Promisereject时传递的错误对象结构,包含错误消息、原始错误对象、是否为网络错误标志以及状态码等信息。

(三)核心HTTP请求函数

// --- 核心 HTTP 请求函数 ---

/**
 * 发起一个 HTTP 请求 (基于 uni.request),并带有 503 自动重试逻辑。
 *
 * @template T - 期望的响应数据 `data` 的类型,默认为 `any`。
 * @param {RetryRequestOptions} options - 请求配置,包含 uni.request 的标准选项以及自定义的重试选项。
 * @returns {Promise<T>} - 返回一个 Promise。成功时 resolve 响应的 `data` 部分;失败时 reject 一个 `HttpRequestError` 对象。
 */
export const http = <T = any>(options: RetryRequestOptions): Promise<T> => {
  // 解构选项,分离出重试配置和标准的 uni.request 配置
  const {
    retries = DEFAULT_MAX_RETRIES,        // 获取重试次数,若未提供则使用默认值
    retryDelay = DEFAULT_RETRY_DELAY,     // 获取重试延迟,若未提供则使用默认值
    hideErrorToast = false,             // 获取是否隐藏错误提示的标志
    ...requestOptions                   // 剩余的选项作为 uni.request 的标准参数
  } = options;

  /**
   * 内部函数,用于执行单次请求尝试
   * @param currentAttempt - 当前是第几次尝试 (从 0 开始)
   */
  const attemptRequest = (currentAttempt: number): Promise<T> => {
    // 返回一个新的 Promise 来包装单次 uni.request 调用
    return new Promise<T>((resolve, reject) => {
      uni.request({
      ...requestOptions, // 传入标准的 uni.request 选项 (url, method, data, header 等)

        // 请求成功的回调 (HTTP 状态码不一定是 2xx)
        success(res) {
          // --- 成功情况 (状态码 200-299) ---
          if (res.statusCode >= 200 && res.statusCode < 300) {
            // 假设服务器返回的有效数据在 res.data 中
            resolve(res.data as T); // 使用 T 类型断言
            return; // 成功,退出回调
          }

          // --- 需要重试的情况 (状态码 503 且 未达到最大重试次数) ---
          if (res.statusCode === 503 && currentAttempt < retries) {
            const attemptNum = currentAttempt + 1; // 计算下一次尝试的序号
            console.warn(
              `[HTTP] 请求失败,状态码 503。将在 ${retryDelay}ms 后进行第 ${attemptNum}/${retries} 次重试... URL: ${requestOptions.url}`
            );
            // 设置延迟执行下一次尝试
            setTimeout(() => {
              // 递归调用 attemptRequest,并将结果通过 then/catch 传递给外层 Promise
              attemptRequest(attemptNum).then(resolve).catch(reject);
            }, retryDelay);
            return; // 等待重试,退出当前回调
          }

          // --- 其他失败情况 (非 2xx 状态码,或 503 重试次数已用尽) ---
          // 构造错误消息
          const errorMessage = `请求失败 [状态码: ${res.statusCode}]`;
          console.error(`[HTTP] 请求错误 ${res.statusCode}: ${requestOptions.url}`, res);
          // 如果用户没有选择隐藏提示,则显示 Toast
          if (!hideErrorToast) {
            uni.showToast({
              icon: 'none',
              // 尝试从响应体中获取更具体的错误信息,否则使用通用信息
              title: (res.data as any)?.message || errorMessage,
              duration: 2000,
            });
          }
          // 用结构化的错误对象 reject Promise
          reject({
            message: errorMessage,
            originalError: res, // 保留原始响应对象
            statusCode: res.statusCode, // 记录状态码
          } as HttpRequestError);
        },

        // 请求失败的回调 (网络层面的错误,例如 DNS 解析失败、网络不可达等)
        fail(err) {
          // --- 网络错误情况 ---
          const errorMessage = '网络连接错误,请检查网络设置或稍后重试';
          console.error(`[HTTP] 网络错误: ${requestOptions.url}`, err);
          // 如果用户没有选择隐藏提示,则显示 Toast
          if (!hideErrorToast) {
            uni.showToast({
              icon: 'none',
              title: errorMessage,
              duration: 2000,
            });
          }
          // 用结构化的错误对象 reject Promise
          reject({
            message: errorMessage,
            originalError: err, // 保留原始错误对象
            isNetworkError: true, // 标记为网络错误
          } as HttpRequestError);
        },

        // complete 回调在 success 或 fail 后都会执行,此处 Promise 的 resolve/reject 已处理最终状态,故无需操作
        // complete() { }
      });
    });
  };

  // 发起第一次请求尝试 (currentAttempt = 0)
  return attemptRequest(0);
};

http函数是模块的核心,它基于uni.request进行封装,并实现了503自动重试逻辑。函数接收一个RetryRequestOptions类型的参数options,通过解构获取重试配置和标准的uni.request配置。内部定义的attemptRequest函数用于执行单次请求尝试,根据请求结果(成功、需要重试、其他失败情况)进行不同处理。成功时,若状态码在200 – 299之间,解析响应数据并resolve;遇到503状态码且未达到最大重试次数时,设置延迟后递归调用attemptRequest进行重试;其他失败情况则构造错误消息,根据配置决定是否显示Toast,并reject一个HttpRequestError对象。最后,http函数发起第一次请求尝试(currentAttempt = 0)并返回attemptRequest(0)的结果。

(四)便捷的辅助函数

// --- 便捷的辅助函数 ---

/** 定义辅助函数的选项类型,排除掉 url, method, data 这三个由辅助函数自身处理的参数 */
type HttpHelperOptions = Omit<RetryRequestOptions, 'url' | 'method' | 'data'>;

/**
 * 发起 GET 请求 (带 503 重试)
 * @template T - 期望的响应数据类型
 * @param url - 请求地址
 * @param data - 查询参数 (uni.request 中 GET 请求的 query 参数通常放在 data 对象里)
 * @param options - 其他配置,如 headers, retries, hideErrorToast 等
 */
export const httpGet = <T = any>(
  url: string,
  data?: UniApp.RequestOptions['data'],
  options?: HttpHelperOptions
): Promise<T> => {
  return http<T>({
    url,
    data, // 对于 GET, uni.request 将 data 作为 query 参数附加到 URL
    method: 'GET',
    ...options, // 合并其他选项
  });
};

/**
 * 发起 POST 请求 (带 503 重试)
 * @template T - 期望的响应数据类型
 * @param url - 请求地址
 * @param data - 请求体数据
 * @param options - 其他配置
 */
export const httpPost = <T = any>(
  url: string,
  data?: UniApp.RequestOptions['data'],
  options?: HttpHelperOptions
): Promise<T> => {
  return http<T>({
    url,
    data, // 请求体
    method: 'POST',
    ...options,
  });
};

/**
 * 发起 PUT 请求 (带 503 重试)
 * @template T - 期望的响应数据类型
 * @param url - 请求地址
 * @param data - 请求体数据
 * @param options - 其他配置
 */
export const httpPut = <T = any>(
  url: string,
  data?: UniApp.RequestOptions['data'],
  options?: HttpHelperOptions
): Promise<T> => {
  return http<T>({
    url,
    data, // 请求体
    method: 'PUT',
    ...options,
  });
};

/**
 * 发起 DELETE 请求 (带 503 重试)
 * @template T - 期望的响应数据类型
 * @param url - 请求地址
 * @param data - 查询参数或请求体 (根据后端 API 设计,DELETE 有时也用 data 传递参数)
 * @param options - 其他配置
 */
export const httpDelete = <T = any>(
  url: string,
  data?: UniApp.RequestOptions['data'],
  options?: HttpHelperOptions
): Promise<T> => {
  return http<T>({
    url,
    data,
    method: 'DELETE',
    ...options,
  });
};

// (可选) 将辅助函数附加到主 http 函数上,提供类似 axios 的调用风格
http.get = httpGet;
http.post = httpPost;
http.put = httpPut;
http.delete = httpDelete;

这部分代码定义了httpGethttpPosthttpPuthttpDelete四个辅助函数,它们基于核心的http函数,为常见的HTTP请求方法提供了更便捷的调用方式。每个辅助函数接收URL、数据(GET请求的查询参数或其他请求的请求体数据)和其他配置选项,然后调用http函数并传入相应参数。最后,通过将这些辅助函数附加到主http函数上,实现了类似axios的调用风格,方便开发者使用。

五、总结

这个UniApp的HTTP请求封装模块,通过提供503自动重试、灵活配置、Promise化处理、辅助函数等功能,为我们在处理网络请求时提供了极大的便利,不仅简化了开发流程,还增强了应用对后端临时故障的容错能力,是UniApp项目中处理HTTP请求的一个优秀选择。


版权声明:本站文章,如无说明,均为本站原创,转载请注明文章来源。如有侵权,请联系博主删除。
本文链接:https://www.panziye.com/front/17311.html
喜欢 (0)
请潘老师喝杯Coffee吧!】
分享 (0)
用户头像
发表我的评论
取消评论
表情 贴图 签到 代码

Hi,您需要填写昵称和邮箱!

  • 昵称【必填】
  • 邮箱【必填】
  • 网址【可选】