在当今前端开发中,Fetch API 已取代 XMLHttpRequest 成为数据请求的标准方案。然而,许多团队在实际项目中仍面临一个共性痛点:每个组件或模块中反复编写相似的请求逻辑,错误处理散落各处,维护成本居高不下。如何构建一个可复用的 Fetch API 包装函数,并实现集中化的错误处理?这已成为提升前端工程化水平的关键一环。

痛点:散落的请求与重复的异常处理

使用原生 Fetch 时,开发者通常需要在每次调用中手动处理响应状态码、网络异常、JSON 解析错误等。例如,一个简单的 GET 请求就需要写:fetch(url).then(res => { if (!res.ok) throw new Error(res.status); return res.json(); }).catch(err => { /* 弹窗提示 */ })。当项目中有几十个接口时,类似的代码会重复出现,不仅冗余,还容易遗漏错误处理逻辑。一旦业务需求变更(如统一添加请求头、增加超时控制),开发者需要逐个文件修改,极易引发生产事故。

解决方案:构建统一请求层

业内最佳实践是创建一个名为 httpClientapiClient 的包装模块,将所有通用逻辑封装在内。该模块对外暴露 getpostputdelete 等方法,内部统一处理请求头拼接、超时控制、响应解析和错误降级。

以下是一个轻量级实现示例:

class HttpClient {
  constructor(baseURL = '', defaultHeaders = {}) {
    this.baseURL = baseURL;
    this.defaultHeaders = defaultHeaders;
  }

  async request(method, url, data = null, options = {}) {
    const config = {
      method,
      headers: { ...this.defaultHeaders, ...options.headers },
      body: data ? JSON.stringify(data) : undefined,
      signal: options.signal || null,
    };

    try {
      const response = await fetch(`${this.baseURL}${url}`, config);
      if (!response.ok) {
        // 集中处理HTTP错误
        const errorData = await response.json().catch(() => null);
        throw new HttpClientError(
          response.status,
          errorData?.message || `请求失败 (${response.status})`
        );
      }
      return await response.json();
    } catch (error) {
      if (error instanceof HttpClientError) throw error;
      // 捕获网络异常(断网、超时等)
      throw new HttpClientError(0, '网络连接异常,请稍后重试');
    }
  }

  get(url, options) { return this.request('GET', url, null, options); }
  post(url, data, options) { return this.request('POST', url, data, options); }
  // ... 其他方法
}

class HttpClientError extends Error {
  constructor(statusCode, message) {
    super(message);
    this.statusCode = statusCode;
  }
}

该封装实现了三大核心设计:集中错误处理——所有 HTTP 状态码异常和网络异常均转换为自定义错误类型,业务代码只需捕获 HttpClientError统一请求配置——baseURL 和默认 headers 只需实例化一次;可扩展性——通过 options 参数可透传 AbortSignal(用于取消请求)或自定义头部。

进阶:超时重试与认证集成

在实际工程中,包装函数还可进一步扩展。例如,借助 AbortController 实现请求超时控制:在 request 方法内创建 AbortController,并用 setTimeout 触发 abort();同时支持自动重试机制,当遇到网络波动时按指数退避策略重试 2~3 次。对于需要携带 Token 的鉴权场景,可在 defaultHeaders 中动态注入,并配合拦截器在 Token 过期时刷新或跳转登录。

最佳实践:让错误信息更友好

集中错误处理并不意味着“一刀切”的弹窗提示。建议将 HttpClientError 按状态码分门别类:401 应触发未授权逻辑,403 提示权限不足,500 则统一显示“服务暂不可用”。在实际业务中,还可将错误码和原始错误信息记录到监控平台(如 Sentry),同时通过回调函数或事件总线让不同页面自定义 UI 反馈。

总结:从“写代码”到“设计系统”

构建 Fetch 包装函数是前端工程化中的“最小投资,最大回报”。它不仅能消灭大量重复代码,更让团队形成统一的错误处理规范,降低后期维护心智负担。无论是初创项目的快速迭代,还是大型应用的长期演进,一个健壮的请求抽象层都不可或缺。正如一位资深前端架构师所言:“好的架构不是用来解决今天的问题,而是让明天的问题更容易解决。”现在就动手重构你的 Fetch 调用,让每一行请求代码都具备可复用、可观测、可维护的基因。