原生 JavaScript Fetch API 异步请求与错误捕获标准封装
在日常做小型 Web 页面或纯静态单页应用时,单独为了几个 HTTP 请求引入庞大的 Axios 往往显得过于笨重。现代浏览器的 fetch 已经非常成熟,但原生的 fetch 在遇到 HTTP 404 或 500 状态码时并不会自动触发 Promise reject,且原生不带超时取消机制。本文给出一个轻巧实用的原生封装。
一、核心封装代码(带超时与异常处理)
/**
* 现代轻量级 Fetch 封装
* @param {string} url 请求地址
* @param {object} options fetch 参数配置
* @param {number} timeout 超时时间 (毫秒),默认 8000ms
*/
async function request(url, options = {}, timeout = 8000) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeout);
const defaultHeaders = {
'Content-Type': 'application/json',
'Accept': 'application/json'
};
try {
const response = await fetch(url, {
...options,
signal: controller.signal,
headers: {
...defaultHeaders,
...options.headers
}
});
// 重点:显式判断 HTTP 状态码
if (!response.ok) {
throw new Error(`HTTP 异常 [状态码: ${response.status}]`);
}
// 自动根据 Content-Type 解析响应体
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('application/json')) {
return await response.json();
}
return await response.text();
} catch (error) {
if (error.name === 'AbortError') {
throw new Error(`请求超时,超过了 ${timeout}ms 限制`);
}
throw error;
} finally {
clearTimeout(timer);
}
}
二、日常调用示例
// 1. GET 请求
async function loadUserData() {
try {
const data = await request('https://api.example.com/user/profile');
console.log('获取数据成功:', data);
} catch (err) {
console.error('加载失败:', err.message);
}
}
// 2. POST 提交 JSON 数据
async function updateSettings(payload) {
try {
const res = await request('https://api.example.com/settings', {
method: 'POST',
body: JSON.stringify(payload)
}, 5000);
alert('保存成功');
} catch (err) {
alert(`操作失败: ${err.message}`);
}
}
三、为什么不要对 404 掉以轻心?
原生 fetch 的设计哲学是:只要服务器有网络层面的响应(哪怕返回 404 Not Found 或 502 Bad Gateway),它都认为“网络通讯成功”并进入 resolve 状态。只有在断网、DNS 解析失败或跨域被拦截时才会 reject。因此上述封装中的 if (!response.ok) 拦截是稳健业务必不可少的一环。