网络请求库通用封装(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,降低重复代码

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

相关推荐
小雨青年1 小时前
智能交互新范式:拒绝“黑盒”,带你用 MateChat 与 DSL 构建“高可靠”的 NL2UI 引擎
前端·ai·华为云
anyup1 小时前
🔥牛逼!3分钟生成 5 套主题,还能一键切换暗黑模式!
前端·前端框架·uni-app
进击的明明1 小时前
深入讨论前端开发中的跨域问题🤔
前端
正在走向自律1 小时前
企业微信消息推送全链路实战:Java后端与Vue前端集成指南
前端·vue.js·企业微信·企业微信消息推送·官方企业微信
_一两风1 小时前
《从一道“诡异”输出题,彻底搞懂 JavaScript 的作用域与执行上下文》
前端·ecmascript 6
lcc1871 小时前
Vue3 CompositionAPI的优势
前端·vue.js
五号厂房1 小时前
聊一聊前端下载文件N种方式
前端
code_Bo1 小时前
使用micro-app 多层嵌套的问题
前端·javascript·架构
小灰1 小时前
VS Code 插件 Webview 热更新配置
前端·javascript