网络请求库通用封装(GET/POST + 超时 + 传参)+fetch

在前端使用 fetch 进行 GET/POST 请求时,传参方式超时设置是高频需求,以下是系统化的实现方案,包含完整示例和避坑点:

一、GET 请求传参

GET 请求的参数需拼接在 URL 末尾(查询字符串),核心注意:

  1. 参数需用 encodeURIComponent 编码(避免特殊字符 / 中文乱码);
  2. 多个参数用 & 分隔,空值 /undefined 建议过滤(避免无效参数)。

实现方式:

1. 手动拼接(简单场景)
js 复制代码
// 基础示例
async function fetchGetSimple() {
  const baseUrl = '/api/user/list';
  // 待传参数
  const params = {
    page: 1,
    size: 10,
    keyword: '张三' // 含中文,需编码
  };

  // 拼接查询字符串(手动编码)
  const queryStr = `page=${encodeURIComponent(params.page)}&size=${encodeURIComponent(params.size)}&keyword=${encodeURIComponent(params.keyword)}`;
  const url = `${baseUrl}?${queryStr}`;

  try {
    const response = await fetch(url);
    const data = await response.json();
    console.log('GET请求结果:', data);
  } catch (error) {
    console.error('请求失败:', error);
  }
}
2. 通用工具函数(推荐,适配多参数)

封装参数拼接逻辑,避免重复代码:

js 复制代码
/**
 * 拼接GET请求的URL和参数
 * @param {string} url 基础URL
 * @param {object} params 参数对象
 * @returns {string} 拼接后的URL
 */
function buildGetUrl(url, params = {}) {
  const validParams = Object.entries(params)
    .filter(([_, value]) => value !== undefined && value !== null && value !== '') // 过滤空值
    .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`);

  return validParams.length ? `${url}?${validParams.join('&')}` : url;
}

// 调用示例
async function fetchGetWithTool() {
  const url = buildGetUrl('/api/user/list', {
    page: 1,
    size: 10,
    keyword: '张三',
    emptyKey: '' // 会被过滤
  });

  try {
    const response = await fetch(url);
    const data = await response.json();
    console.log('GET请求结果:', data);
  } catch (error) {
    console.error('请求失败:', error);
  }
}

二、POST 请求传参

POST 请求参数需放在 fetchbody 中,核心区分 Content-Type(决定参数格式),常见 3 种场景:

1. JSON 格式(最常用,后端接收 application/json

js 复制代码
async function fetchPostJson() {
  const url = '/api/user/add';
  // POST参数(JSON格式)
  const postData = {
    name: '李四',
    age: 25,
    tags: ['前端', 'JavaScript']
  };

  try {
    const response = await fetch(url, {
      method: 'POST', // 必须指定方法
      headers: {
        'Content-Type': 'application/json' // 声明JSON格式
        // 可选:添加token等自定义头
        // 'Authorization': 'Bearer ' + token
      },
      body: JSON.stringify(postData) // 转为JSON字符串
    });

    const data = await response.json();
    console.log('POST(JSON)请求结果:', data);
  } catch (error) {
    console.error('请求失败:', error);
  }
}

2. 表单格式(application/x-www-form-urlencoded,模拟表单提交)

js 复制代码
async function fetchPostForm() {
  const url = '/api/user/login';
  // 表单参数
  const formData = {
    username: 'admin',
    password: '123456'
  };

  // 转为表单字符串(key1=value1&key2=value2)
  const formStr = new URLSearchParams(formData).toString();
  // 等价于:Object.entries(formData).map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join('&')

  try {
    const response = await fetch(url, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded' // 声明表单格式
      },
      body: formStr
    });

    const data = await response.json();
    console.log('POST(表单)请求结果:', data);
  } catch (error) {
    console.error('请求失败:', error);
  }
}

3. FormData 格式(上传文件 / 多表单数据)

js 复制代码
async function fetchPostFormData() {
  const url = '/api/file/upload';
  // 创建FormData对象
  const formData = new FormData();
  formData.append('file', document.querySelector('#fileInput').files[0]); // 文件
  formData.append('name', '上传文件'); // 普通参数

  try {
    const response = await fetch(url, {
      method: 'POST',
      body: formData // 无需设置Content-Type,浏览器自动添加multipart/form-data
    });

    const data = await response.json();
    console.log('文件上传结果:', data);
  } catch (error) {
    console.error('上传失败:', error);
  }
}

三、fetch 超时设置

fetch 原生无直接的 timeout 参数 ,需通过 Promise.race 实现超时控制:

  • 原理:同时执行 fetch 请求延时reject的Promise,谁先完成就执行谁;
  • 超时后需终止请求 (避免无效请求占用资源),通过 AbortController 实现。

完整超时实现(含请求终止)

js 复制代码
/**
 * 带超时的fetch请求
 * @param {string} url 请求URL
 * @param {object} options fetch配置(method/headers/body等)
 * @param {number} timeout 超时时间(毫秒,默认5000)
 * @returns {Promise} 请求结果
 */
async function fetchWithTimeout(url, options = {}, timeout = 5000) {
  // 创建AbortController(用于终止请求)
  const controller = new AbortController();
  const signal = controller.signal;

  // 超时Promise:timeout毫秒后reject,并终止请求
  const timeoutPromise = new Promise((_, reject) => {
    setTimeout(() => {
      controller.abort(); // 终止fetch请求
      reject(new Error(`请求超时(${timeout}ms)`));
    }, timeout);
  });

  // 用Promise.race包裹fetch和超时Promise
  const fetchPromise = fetch(url, {
    ...options,
    signal // 关联AbortController
  });

  try {
    const response = await Promise.race([fetchPromise, timeoutPromise]);
    // 检查请求是否成功(status 200-299)
    if (!response.ok) {
      throw new Error(`请求失败:${response.status} ${response.statusText}`);
    }
    // 根据返回类型解析(可扩展为自动识别JSON/Blob/Text)
    const contentType = response.headers.get('Content-Type');
    if (contentType?.includes('application/json')) {
      return await response.json();
    }
    return await response.text();
  } catch (error) {
    // 区分超时错误和终止错误
    if (error.name === 'AbortError') {
      throw new Error('请求被终止(超时/手动取消)');
    }
    throw error;
  }
}

// 调用示例1:GET请求 + 超时(3秒)
fetchWithTimeout(
  buildGetUrl('/api/user/list', { page: 1, size: 10 }),
  { method: 'GET' },
  3000
)
  .then(data => console.log('GET请求结果:', data))
  .catch(error => console.error('GET请求失败:', error));

// 调用示例2:POST请求 + 超时(5秒)
fetchWithTimeout(
  '/api/user/add',
  {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ name: '李四', age: 25 })
  },
  5000
)
  .then(data => console.log('POST请求结果:', data))
  .catch(error => console.error('POST请求失败:', error));

四、关键注意事项

  1. 参数编码

    • GET 参数和表单格式的 POST 参数必须编码(encodeURIComponent),避免中文 / 特殊字符(如 &=、空格)导致参数解析错误;
    • JSON 格式的 POST 参数无需手动编码,JSON.stringify 会自动处理。
  2. AbortController 兼容性

    • 支持 Chrome 66+、Firefox 57+、Edge 79+、Safari 12.1+,老旧浏览器(如 IE)需 polyfill;
    • 超时后必须调用 controller.abort(),否则 fetch 仍会继续请求,浪费资源。
  3. 超时错误区分

    • 超时触发的 AbortError 和手动取消请求的错误类型一致,可通过自定义 Error 信息区分(如示例中的 请求超时 提示)。
  4. POST 请求 Content-Type 注意

    • JSON 格式:必须设置 Content-Type: application/json,否则后端无法解析;
    • FormData 格式:禁止手动设置 Content-Type ,浏览器会自动添加边界符(boundary),手动设置会导致后端解析失败。
  5. 空参数处理

    • GET 请求建议过滤 undefined/null/空字符串 参数,避免 URL 出现 ?key=&key2= 这类无效拼接;
    • POST 请求根据后端要求处理空值(部分后端允许接收空字符串,部分需过滤)。

五、通用封装(GET/POST + 超时 + 传参)

整合以上逻辑,封装通用 fetch 函数,适配大部分场景:

js 复制代码
/**
 * 通用fetch请求函数
 * @param {string} url 请求URL
 * @param {object} options 配置项
 * @param {string} options.method 请求方法(GET/POST,默认GET)
 * @param {object} options.params URL参数(GET专用)
 * @param {object} options.data 请求体参数(POST专用)
 * @param {string} options.contentType 请求体格式(json/form,默认json)
 * @param {number} options.timeout 超时时间(毫秒,默认5000)
 * @returns {Promise} 请求结果
 */
async function request({
  url,
  method = 'GET',
  params = {},
  data = {},
  contentType = 'json',
  timeout = 5000
}) {
  // 处理GET参数
  const finalUrl = method.toUpperCase() === 'GET' ? buildGetUrl(url, params) : url;

  // 处理POST请求体
  let body = null;
  const headers = {};
  if (method.toUpperCase() === 'POST') {
    if (contentType === 'json') {
      headers['Content-Type'] = 'application/json';
      body = JSON.stringify(data);
    } else if (contentType === 'form') {
      headers['Content-Type'] = 'application/x-www-form-urlencoded';
      body = new URLSearchParams(data).toString();
    } else if (contentType === 'formData') {
      body = data; // 直接传入FormData对象,无需设置headers
    }
  }

  // 调用带超时的fetch
  return fetchWithTimeout(finalUrl, {
    method,
    headers,
    body
  }, timeout);
}

// 调用示例
// 1. GET请求
request({
  url: '/api/user/list',
  method: 'GET',
  params: { page: 1, size: 10 },
  timeout: 3000
}).then(data => console.log(data)).catch(err => console.error(err));

// 2. POST JSON请求
request({
  url: '/api/user/add',
  method: 'POST',
  data: { name: '李四', age: 25 },
  contentType: 'json',
  timeout: 5000
}).then(data => console.log(data)).catch(err => console.error(err));

// 3. POST 表单请求
request({
  url: '/api/user/login',
  method: 'POST',
  data: { username: 'admin', password: '123456' },
  contentType: 'form',
  timeout: 4000
}).then(data => console.log(data)).catch(err => console.error(err));

总结

场景 实现要点
GET 传参 参数编码 + URL 拼接 + 过滤空值
POST 传参 JSON(常用)/ 表单 / FormData,匹配对应 Content-Type
超时设置 Promise.race + AbortController(必须终止请求)
通用封装 整合参数处理、超时、Content-Type,降低重复代码

核心原则:传参需匹配后端解析格式,超时需终止无效请求,封装需兼顾通用性和可维护性

相关推荐
崔庆才丨静觅2 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60612 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了2 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅2 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅3 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅3 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment3 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅4 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊4 小时前
jwt介绍
前端
爱敲代码的小鱼4 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax