在前端使用 fetch 进行 GET/POST 请求时,传参方式 和超时设置是高频需求,以下是系统化的实现方案,包含完整示例和避坑点:
一、GET 请求传参
GET 请求的参数需拼接在 URL 末尾(查询字符串),核心注意:
- 参数需用
encodeURIComponent编码(避免特殊字符 / 中文乱码); - 多个参数用
&分隔,空值 /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 请求参数需放在 fetch 的 body 中,核心区分 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));
四、关键注意事项
-
参数编码:
- GET 参数和表单格式的 POST 参数必须编码(
encodeURIComponent),避免中文 / 特殊字符(如&、=、空格)导致参数解析错误; - JSON 格式的 POST 参数无需手动编码,
JSON.stringify会自动处理。
- GET 参数和表单格式的 POST 参数必须编码(
-
AbortController 兼容性:
- 支持 Chrome 66+、Firefox 57+、Edge 79+、Safari 12.1+,老旧浏览器(如 IE)需 polyfill;
- 超时后必须调用
controller.abort(),否则 fetch 仍会继续请求,浪费资源。
-
超时错误区分:
- 超时触发的
AbortError和手动取消请求的错误类型一致,可通过自定义 Error 信息区分(如示例中的请求超时提示)。
- 超时触发的
-
POST 请求 Content-Type 注意:
- JSON 格式:必须设置
Content-Type: application/json,否则后端无法解析; - FormData 格式:禁止手动设置 Content-Type ,浏览器会自动添加边界符(
boundary),手动设置会导致后端解析失败。
- JSON 格式:必须设置
-
空参数处理:
- GET 请求建议过滤
undefined/null/空字符串参数,避免 URL 出现?key=&key2=这类无效拼接; - POST 请求根据后端要求处理空值(部分后端允许接收空字符串,部分需过滤)。
- GET 请求建议过滤
五、通用封装(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,降低重复代码 |
核心原则:传参需匹配后端解析格式,超时需终止无效请求,封装需兼顾通用性和可维护性。