学习笔记:封装fetch函数的实现与优化

169 阅读3分钟

一、背景与需求

在日常开发中,我们频繁使用 fetch 来发起 HTTP 请求。然而,原生 fetch 代码往往较为冗长,不够灵活,且需要重复处理一些常见场景,例如:

  • 设置默认请求头
  • 自动处理 GET 请求的查询参数
  • 处理请求体的序列化
  • 统一的错误处理

为了解决这些问题,我封装了一个通用的 fetchApi 函数,以简化使用,并提高代码的复用性和可维护性。


二、代码实现

以下是封装后的 fetchApi 函数:

import { getGlobalData } from './data.js';

export default function fetchApi(url, options = {}) {
  const defaultOptions = {
    method: 'GET',
    headers: {
      'Content-Type': 'application/json;charset=utf-8',
    },
    dataType: 'json',
  };

  const mergedOptions = { ...defaultOptions, ...options };

  // 正确处理 POST 和 PUT 请求的 body
  if (['POST', 'PUT'].includes(mergedOptions.method) && typeof mergedOptions.body === 'object') {
    mergedOptions.body = JSON.stringify(mergedOptions.body);
    mergedOptions.headers['Content-Type'] = 'application/json';
  }

  let fullUrl = getGlobalData('baseUrlPrefix') + url;
  if (mergedOptions.method === 'GET' && mergedOptions.params) {
    const queryParams = new URLSearchParams(mergedOptions.params).toString();
    fullUrl += (fullUrl.includes('?') ? '&' : '?') + queryParams;
  }

  return fetch(fullUrl, mergedOptions)
    .then(response => {
      if (response.ok) {
        return mergedOptions.dataType === 'json' ? response.json() : response;
      } else {
        throw new Error(`HTTP 错误!状态码:${response.status}`);
      }
    })
    .catch(error => {
      console.error('网络错误:', error);
      throw error;
    });
}

三、功能分析

  1. 默认配置的设计:
    • defaultOptions 提供了默认的 HTTP 请求方法、请求头以及数据类型,避免在每次调用时重复设置。
    • 使用 ... 语法合并用户自定义配置,使得默认值与自定义选项共存。
  2. 自动处理请求体:
    • POSTPUT 请求,如果 body 是一个对象,会自动序列化为 JSON,并设置正确的 Content-Type
  3. 构造完整 URL:
    • getGlobalData('baseUrlPrefix') 提供基础的 URL 前缀,保证所有请求可以统一管理。
    • GET 请求时,如果有 params,会自动将对象序列化为查询字符串并拼接到 URL。
  4. 统一的错误处理:
    • 当响应状态码不在 200-299 范围内时,抛出错误。
    • 捕获所有网络异常,并输出到控制台。
  5. 灵活的返回数据类型:
    • 支持返回 JSON 格式,也可以直接返回原始响应对象。

四、使用示例

以下是几个典型的使用场景:

  1. 发起 GET 请求:

    javascript复制代码fetchApi('/users', { params: { page: 1, size: 10 } })
      .then(data => console.log('用户数据:', data))
      .catch(error => console.error('请求失败:', error));
    
  2. 发起 POST 请求:

    javascript复制代码fetchApi('/login', {
      method: 'POST',
      body: { username: 'admin', password: '123456' },
    })
      .then(data => console.log('登录成功:', data))
      .catch(error => console.error('登录失败:', error));
    
  3. 发起 PUT 请求:

    javascript复制代码fetchApi('/users/123', {
      method: 'PUT',
      body: { name: 'John Doe' },
    })
      .then(data => console.log('更新成功:', data))
      .catch(error => console.error('更新失败:', error));
    

五、优点与改进方向

优点:
  1. 简洁易用: 将重复逻辑封装,减少了每次发请求时的代码量。
  2. 灵活扩展: 提供了 options 参数,方便根据不同场景自定义请求。
  3. 错误处理统一: 提供了统一的错误处理机制,便于调试与维护。
  4. 模块化: 使用独立的函数封装,方便复用。
改进方向:
  1. 支持更多数据格式:
    • 除了 json,可以扩展对其他格式(如 textblob)的支持。
    • dataType 中添加更多选项,如 dataType: 'text'
  2. 添加超时功能:
    • 原生 fetch 不支持超时,可以通过 Promise.raceAbortController 实现。
  3. 更灵活的错误处理:
    • 允许用户传入自定义的错误处理回调。
  4. 加入请求拦截器与响应拦截器:
    • 类似于 Axios,可以在请求发送前或收到响应后执行一些额外的逻辑。

六、总结

封装的 fetchApi 函数实现了对原生 fetch 的增强,解决了许多实际开发中的痛点。它简化了 HTTP 请求逻辑,提高了代码的可读性和可维护性。未来可以通过添加超时功能和拦截器进一步优化,以满足更多复杂场景的需求。

通过本次封装,我更加熟悉了 fetch 的基本用法、JavaScript 的对象合并技巧以及异常处理的设计原则。