- Fetch API 是浏览器原生提供的新一代网络请求接口,用于替代传统的 XMLHttpRequest (XHR);
- 它基于 Promise 设计,支持异步 / 同步请求、请求 / 响应拦截、流式处理等特性,语法更简洁、功能更强大,是现代前端开发中网络请求的首选方案;
核心概念
基本定义
-
Fetch API 提供了一个全局的 fetch() 方法,用于发起网络请求并获取资源;
-
它的核心特点:
- 基于 Promise,支持 async/await 语法,异步流程更清晰;
- 仅在 网络错误 (如断网、跨域限制、服务器无响应) 时 reject,HTTP 错误 (4xx/5xx) 不会触发 reject;
- 默认不携带 Cookie,需手动配置;
- 不支持超时控制,需手动实现;
- 支持 Request/Response 对象,可更灵活地配置请求和处理响应;
与 XMLHttpRequest 的区别
| 特性 | Fetch API | XMLHttpRequest |
|---|---|---|
语法风格 |
Promise 化,支持 async/await | 回调函数 (回调地狱风险) |
错误处理 |
仅网络错误 reject | 状态码错误可通过 status 判断 |
Cookie 携带 |
默认不携带,需配置 | 默认携带 |
超时控制 |
无原生支持,需手动实现 | 原生 timeout 属性 |
中止请求 |
支持 (AbortController) | 支持 (abort() 方法) |
响应处理 |
需手动解析 (json/text/blob) | 自动解析响应体 |
扩展性 |
支持 Request/Response 封装 | 配置繁琐,扩展性差 |
基本用法
语法结构
-
语法结构
fetch(resource, options) .then(response => { // 处理响应(需先解析响应体) if (!response.ok) { throw new Error(`HTTP 错误!状态码:${response.status}`); } return response.json(); // 解析为 JSON(或 text/blob 等) }) .then(data => { // 处理解析后的数据 console.log('请求成功:', data); }) .catch(error => { // 捕获网络错误或自定义错误 console.error('请求失败:', error); }); -
参数说明:
- resource:请求地址 (字符串) 或 Request 对象;
- options (可选):请求配置对象,常用属性如下:
属性 类型 说明 默认值 method 字符串 请求方法 (GET/POST/PUT/DELETE 等) “GET” headers 对象 / Headers 请求头 (如 Content-Type、Authorization) {} body 字符串 / Blob / FormData 等 请求体 (POST/PUT 等方法用) null (GET 无) mode 字符串 跨域模式 (cors/no-cors/same-origin) “cors” credentials 字符串 Cookie 携带策略 (include/same-origin/omit) “same-origin” cache 字符串 缓存策略 (default/no-store/reload 等) “default” signal AbortSignal 用于中止请求 (配合 AbortController) undefined
常见请求示例
// 简洁写法
fetch('https://api.example.com/data')
.then(res => {
if (!res.ok) throw new Error('请求失败');
return res.json(); // 解析 JSON 响应体
})
.then(data => console.log(data))
.catch(err => console.error(err));
// 带查询参数的 GET 请求
const params = new URLSearchParams({ id: 1, name: 'test' });
fetch(`https://api.example.com/data?${params}`)
.then(res => res.json())
.then(data => console.log(data));
fetch('https://api.example.com/submit', {
method: 'POST',
headers: {
'Content-Type': 'application/json', // 告诉服务器请求体是 JSON 格式
'Authorization': 'Bearer your-token' // 可选:身份验证令牌
},
body: JSON.stringify({ username: 'admin', password: '123456' }), // 序列化 JSON
credentials: 'include' // 携带跨域 Cookie(如需)
})
.then(res => {
if (!res.ok) throw new Error(`状态码:${res.status}`);
return res.json();
})
.then(data => console.log('提交成功:', data))
.catch(err => console.error('提交失败:', err));
// 方式 1:FormData 对象(适合文件上传或表单提交)
const formData = new FormData();
formData.append('username', 'admin');
formData.append('avatar', fileInput.files[0]); // 上传文件
fetch('https://api.example.com/upload', {
method: 'POST',
body: formData, // 无需设置 Content-Type,浏览器自动添加 multipart/form-data
credentials: 'include'
})
.then(res => res.json())
.then(data => console.log('上传成功:', data));
// 方式 2:URLSearchParams(适合 application/x-www-form-urlencoded 格式)
const formParams = new URLSearchParams();
formParams.append('username', 'admin');
formParams.append('password', '123456');
fetch('https://api.example.com/login', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: formParams
})
.then(res => res.json())
.then(data => console.log('登录成功:', data));
// PUT 请求(更新数据)
fetch('https://api.example.com/data/1', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'updated' })
})
.then(res => res.json())
.then(data => console.log('更新成功:', data));
// DELETE 请求(删除数据)
fetch('https://api.example.com/data/1', { method: 'DELETE' })
.then(res => {
if (res.ok) console.log('删除成功');
});
Request 对象
-
除了使用基本的 fetch 方法,还可以通过创建一个 Request 对象来完成请求(fetch 的内部会创建一个 Request 对象);
let req = new Request(url[, 配置]); fetch(req) -
尽量保证每次请求都是一个新的 Request 对象;
let req; function getRequestInfo() { if (!req) { const url = 'http://study.yuanjin.tech/api/local'; req = new Request(url, {}); console.log(req); } return req.clone(); // 克隆一个全新的 request 对象,配置一致 } async function getProvinces() { const resp = await fetch(getRequestInfo()); const result = await resp.json(); console.log(result); } document.querySelector('button').onclick = function () { getProvinces(); };
Headers 对象
-
在 Request 和 Response 对象内部,会将传递的请求头对象转换为 Headers;
-
Headers 对象中的方法
方法 描述 has(key) 检查请求头中是否存在指定的 key 值 get(key) 得到请求头中对应的 key 值 set(key, value) 修改对应的键值对 append(key, value) 添加对应的键值对 keys() 得到所有的请求头键的集合 values() 得到所有的请求头中的值的集合 entries() 得到所有请求头中的键值对的集合 -
示例代码
let req; function getCommonHeaders() { return new Headers({ a: 1, b: 2, }); } function getRequestInfo() { if (!req) { req = new Request('http://study.yuanjin.tech/api/local', { headers: getCommonHeaders() }); } return req.clone(); // 克隆一个全新的 request 对象,配置一致 } async function getProvinces() { const resp = await fetch(getRequestInfo()); const result = await resp.json(); console.log(result); } document.querySelector('button').onclick = function () { getProvinces(); };
响应处理
fetch() 成功后返回的 response 对象是一个 Response 实例,包含响应状态、响应头、响应体等信息,但响应体需通过专门的方法解析;
Response 核心属性
| 属性 | 说明 |
|---|---|
| ok | 布尔值,status 在 200-299 之间时为 true |
| status | HTTP 状态码 (如 200、404、500) |
| statusText | 状态文本 (如 “OK”、“Not Found”) |
| headers | 响应头 (Headers 对象,支持 get() 方法获取) |
| url | 最终响应的 URL (可能因重定向变化) |
| redirected | 是否发生重定向 |
响应体解析方法(均返回 Promise)
-
根据响应数据类型选择对应的解析方法:
方法 说明 适用场景 response.json() 解析为 JSON 对象 接口返回 JSON 数据 (最常用) response.text() 解析为字符串 纯文本、HTML 等 response.blob() 解析为 Blob 对象 (二进制数据) 图片、音频、视频等文件 response.formData() 解析为 FormData 对象 表单提交后的响应 response.arrayBuffer() 解析为 ArrayBuffer (二进制缓冲区) 处理二进制数据 (如加密) -
示例:下载图片并显示
fetch('https://api.example.com/image.jpg') .then(res => { if (!res.ok) throw new Error('图片下载失败'); return res.blob(); // 解析为 Blob 对象 }) .then(blob => { const imgUrl = URL.createObjectURL(blob); // 创建临时 URL const img = document.createElement('img'); img.src = imgUrl; document.body.appendChild(img); }) .catch(err => console.error(err));
高级特性
超时控制
Fetch 无原生超时配置,需通过 Promise.race() / AbortController 实现 (超时后中止请求)
function fetchWithTimeout(resource, options = {}, timeout = 5000) {
// 创建中止控制器
const controller = new AbortController();
const { signal } = controller;
// 超时后中止请求
const timeoutId = setTimeout(() => controller.abort(), timeout);
// 合并 signal 到 options
const fetchOptions = { ...options, signal };
return fetch(resource, fetchOptions)
.then(res => {
clearTimeout(timeoutId); // 请求成功,清除超时计时器
return res;
})
.catch(err => {
clearTimeout(timeoutId);
// 区分超时错误和其他错误
if (err.name === 'AbortError') {
throw new Error(`请求超时(${timeout}ms)`);
}
throw err;
});
}
// 使用示例
fetchWithTimeout('https://api.example.com/data', { method: 'GET' }, 3000)
.then(res => res.json())
.then(data => console.log(data))
.catch(err => console.error(err)); // 超时会抛出 "请求超时(3000ms)"
中止请求
通过 AbortController 可以手动中止正在进行的 Fetch 请求 (如用户取消操作)
const controller = new AbortController();
const { signal } = controller;
// 发起请求
fetch('https://api.example.com/data', { signal })
.then(res => res.json())
.then(data => console.log(data))
.catch(err => {
if (err.name === 'AbortError') {
console.log('请求已被中止');
} else {
console.error(err);
}
});
// 手动中止请求(如点击取消按钮时)
document.getElementById('cancelBtn').addEventListener('click', () => {
controller.abort();
});
跨域请求(CORS)
-
Fetch 默认支持跨域请求 (mode: “cors”),但需服务器配置 CORS 响应头 (如 Access-Control-Allow-Origin);
-
关键配置:
- mode: “cors”:允许跨域请求,服务器需返回 CORS 头;
- mode: “no-cors”:仅允许简单跨域请求 (GET/POST/HEAD,无自定义头),响应无法访问响应体;
- credentials: “include”:跨域请求时携带 Cookie (需服务器配置 Access-Control-Allow-Credentials: true);
fetch('https://cross-domain-api.example.com/data', {
method: 'GET',
credentials: 'include', // 携带跨域 Cookie
mode: 'cors'
})
.then(res => res.json())
.then(data => console.log(data))
.catch(err => console.error(err));
请求 / 响应拦截
Fetch 无原生拦截器,但可通过封装函数实现类似 Axios 的拦截效果;
// 拦截器配置
const interceptors = {
request: (config) => {
// 请求拦截:添加统一请求头、Token 等
config.headers = {
...config.headers,
'Authorization': 'Bearer your-token',
'X-Request-From': 'web'
};
return config;
},
response: (response) => {
// 响应拦截:统一处理成功响应
if (!response.ok) throw new Error(`状态码:${response.status}`);
return response.json();
},
error: (error) => {
// 错误拦截:统一处理错误(如 Token 过期、网络错误)
if (error.message.includes('401')) {
console.log('Token 过期,跳转登录页');
// window.location.href = '/login';
}
return Promise.reject(error);
}
};
// 封装带拦截器的 fetch
function request(resource, options = {}) {
const config = interceptors.request({ resource, ...options });
return fetch(config.resource, config)
.then(interceptors.response)
.catch(interceptors.error);
}
// 使用示例
request('https://api.example.com/data', { method: 'GET' })
.then(data => console.log('请求成功:', data))
.catch(err => console.error('请求失败:', err));
缓存控制
-
通过 options.cache 配置请求的缓存策略 (优先级高于 HTTP 缓存头)
cache 值 说明 default 优先使用缓存,无缓存则请求网络 no-store 不使用缓存,直接请求网络 (不存储响应) reload 不使用缓存,直接请求网络 (存储响应) no-cache 验证缓存有效性,有效则用缓存,否则请求网络 force-cache 强制使用缓存 (即使缓存过期),无缓存才请求网络 only-if-cached 仅使用缓存,无缓存则返回 504 错误 -
示例:禁用缓存
fetch('https://api.example.com/data', { cache: 'no-store' }) .then(res => res.json()) .then(data => console.log(data));
错误处理最佳实践
-
Fetch 的错误处理需注意两点:
- HTTP 错误 (4xx/5xx) 不会触发 catch,需通过 response.ok 手动判断;
- 网络错误 (断网、跨域限制) 会触发 catch,需区分错误类型;
-
示例代码
fetch('https://api.example.com/data') .then(res => { // 处理 HTTP 错误 if (!res.ok) { return res.json().catch(() => res.text()).then(body => { // 携带响应体信息抛出错误(便于调试) throw new Error(`HTTP ${res.status}: ${JSON.stringify(body)}`); }); } return res.json(); }) .then(data => console.log(data)) .catch(err => { // 区分错误类型 if (err.name === 'AbortError') { console.log('请求已中止'); } else if (err.message.includes('Failed to fetch')) { console.log('网络错误(断网/跨域限制)'); } else { console.error('请求失败:', err.message); } });
API 篇:hash 和 history
上一篇