news 2026/7/24 11:28:23

Fetch API 使用及简单封装

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Fetch API 使用及简单封装

Fetch API是现代浏览器提供的用于发起网络请求的原生 JavaScript API。它的设计初衷是替代老旧、基于回调的XMLHttpRequest(XHR),提供更强大、更灵活且基于Promise的异步编程体验。

虽然 Fetch 已经成为现代前端的标配,但它的设计存在一些“反直觉”的地方。以下是对 Fetch 的全面、详细说明。

一、 基础用法

Fetch 的核心是window.fetch()方法。它接收一个 URL 和一个可选的配置对象,返回一个 Promise。

1. GET 请求
// 传统 Promise 写法 fetch('https://api.example.com/users') .then(response => { // 注意:response.json() 返回的依然是一个 Promise return response.json(); }) .then(data => console.log(data)) .catch(error => console.error('Error:', error)); // 现代 async/await 写法 (推荐) async function getUsers() { try { const response = await fetch('https://api.example.com/users'); const data = await response.json(); console.log(data); } catch (error) { console.error('Error:', error); } }
2. POST 请求

POST 请求需要通过options参数配置methodheadersbody

async function createUser() { const userData = { name: '张三', age: 25 }; try { const response = await fetch('https://api.example.com/users', { method: 'POST', headers: { 'Content-Type': 'application/json' // 必须手动声明 JSON 格式 }, body: JSON.stringify(userData) // 必须手动将对象转为 JSON 字符串 }); const result = await response.json(); console.log('Success:', result); } catch (error) { console.error('Error:', error); } }

⚠️ 避坑指南 (FormData):如果你要上传文件,使用了FormData对象作为body千万不要手动设置Content-Type。浏览器会自动将其设置为multipart/form-data并附带正确的boundary。如果你手动设置了,反而会破坏 boundary,导致后端无法解析文件。

二、 响应处理 (Response 对象)

fetchresolve 后返回的是一个Response对象,它包含了服务器的响应头、状态码和响应体。

1. 解析响应体

响应体是流 (Stream),只能被读取一次。根据数据类型,调用不同的方法:

  • response.json():解析为 JSON 对象(最常用)。
  • response.text():解析为纯文本。
  • response.blob():解析为二进制大对象(常用于下载文件、图片预览)。
  • response.arrayBuffer():解析为原始二进制缓冲区(常用于处理音频、视频流)。
2. 常用 Response 属性
  • response.ok(Boolean):极其重要。当 HTTP 状态码在 200-299 之间时为true
  • response.status(Number):HTTP 状态码(如 200, 404, 500)。
  • response.statusText(String):状态信息(如 "OK", "Not Found")。
  • response.headers(Headers 对象):响应头,可通过response.headers.get('Content-Type')获取。

三、 Fetch 的四大“痛点”(为什么我们需要封装?)

尽管 Fetch 是原生 API,但直接裸写 Fetch 往往会带来很多麻烦。以下是它的核心缺陷:

痛点 1:HTTP 错误状态码(4xx, 5xx)不会触发catch

这是新手最容易踩的坑。在 Fetch 的设计中,只有网络故障(如断网、DNS 解析失败、请求被拦截)才会导致 Promise reject。 如果服务器返回了404 Not Found500 Internal Server Error,Fetch 依然会正常 resolve,进入then

fetch('/api/not-found') // 假设返回 404 .then(res => { console.log(res.ok); // false console.log(res.status); // 404 // 注意:这里不会进入 catch! }) .catch(err => { // 只有断网时才会进这里 });

解决方案:必须手动判断response.ok,如果不为 true,则手动抛出错误。

痛点 2:原生不支持超时控制

XHR 可以通过xhr.timeout设置超时,但 Fetch 原生没有这个属性。如果服务器一直不响应,请求会无限期挂起。解决方案:必须借助AbortController结合setTimeout手动实现(如上一个回答中的封装)。

痛点 3:默认不携带 Cookie (跨域时)

在跨域请求时,Fetch 默认不会携带目标域的 Cookie。解决方案:需要显式设置credentials: 'include'

fetch('https://api.other-domain.com/data', { credentials: 'include' // 允许跨域携带 Cookie })
痛点 4:无法直接监听上传/下载进度

XHR 提供了xhr.upload.onprogressxhr.onprogress来轻松实现进度条。Fetch 没有直接的进度事件。解决方案:下载进度可以通过ReadableStream模拟计算;上传进度目前 Fetch 原生支持极差,通常只能退回使用 XHR 或 Axios。

四、 高级特性 (Fetch 的杀手锏)

虽然 Fetch 有痛点,但它提供了 XHR 无法比拟的现代特性:

1. 中断请求 (AbortController)

这是 Fetch 最强大的特性之一,可以轻松取消正在进行的请求(例如用户快速切换页面,或防抖取消上一次请求)。

const controller = new AbortController(); const signal = controller.signal; // 发起请求 fetch('https://api.example.com/slow-data', { signal }) .then(res => res.json()) .then(data => console.log(data)) .catch(err => { if (err.name === 'AbortError') { console.log('请求被手动取消'); } else { console.error('请求失败', err); } }); // 3秒后取消请求 setTimeout(() => { controller.abort(); // 触发取消 }, 3000);
2. 流式读取 (ReadableStream)

Fetch 允许你以流的方式分块读取响应体,这在处理大文件下载SSE (Server-Sent Events) 实时推送时非常有用,不需要等整个文件下载完才开始处理。

fetch('https://example.com/large-file.zip') .then(response => { const reader = response.body.getReader(); const contentLength = +response.headers.get('Content-Length'); let receivedLength = 0; let chunks = []; return (async function read() { while(true) { const { done, value } = await reader.read(); if (done) break; chunks.push(value); receivedLength += value.length; console.log(`下载进度: ${(receivedLength / contentLength * 100).toFixed(2)}%`); } // 将所有块合并为一个 Blob let blob = new Blob(chunks); console.log('下载完成', blob); })(); });

五、 总结与最佳实践

Fetch vs XMLHttpRequest vs Axios

特性

Fetch

XMLHttpRequest (XHR)

Axios

底层实现

现代标准 API

老旧 API

浏览器端基于 XHR,Node 端基于 http

Promise 支持

原生支持

不支持 (需手动封装)

原生支持

数据转换

需手动 (res.json(),JSON.stringify)

需手动

自动转换 JSON

超时控制

需手动 (AbortController)

原生支持 (timeout)

原生支持 (timeout)

进度监听

极难 (需用 Stream 模拟)

原生支持 (onprogress)

原生支持

拦截器

支持 (请求/响应拦截)

取消请求

支持 (AbortController)

支持 (abort())

支持

建议:
  1. 不要在业务代码中裸写 Fetch:因为它的痛点太多(状态码不报错、无超时、无自动 JSON 转换)。
  2. 中小型项目/简单脚本:使用自定义 Fetch 封装类,轻量且无第三方依赖。
  3. 中大型商业项目:建议使用Axios。它完美解决了 Fetch 的所有痛点,提供了拦截器、自动转换、取消请求等工程化能力,且生态极其成熟。
  4. 特殊场景使用原生 Fetch:当你需要处理流式数据 (SSE)大文件分块下载,或者在 Service Worker 中拦截请求时,必须使用原生 Fetch。

六、对Fetch简单封装

由于原生fetch存在一些痛点(如:不支持超时控制、不自动转换 JSON、HTTP 4xx/5xx 不会进入 catch、GET 请求处理参数麻烦)。这个封装解决了这些问题,同时保持了代码的极简。

主要说明:

  1. 自动超时中断:利用AbortController实现了原生fetch缺失的超时控制,防止请求无限挂起。
  2. 智能参数处理GET请求自动将对象序列化为 URL Query 字符串;POST/PUT自动将对象转换为 JSON 字符串。
  3. 状态码校验:原生fetch遇到 404 或 500 依然会进入then,封装后统一在!response.ok时抛出异常,进入catch,符合直觉。
  4. 智能响应解析:自动检测Content-Type,如果是 JSON 则自动调用.json(),否则返回.text(),避免解析报错。
  5. 安全过滤:拼接 URL 参数时,自动过滤掉undefinednull的值,避免产生?key=这样的脏 URL
// http.js /** * 轻量级 Fetch 请求封装 */ class Http { /** * @param {string} baseURL - 基础请求地址 * @param {object} defaultOptions - 默认配置(如全局 headers、timeout) */ constructor(baseURL = '', defaultOptions = {}) { this.baseURL = baseURL; this.defaultOptions = { timeout: 10000, // 默认 10 秒超时 ...defaultOptions, }; } /** * 核心请求方法 * @param {string} url - 请求路径 * @param {object} options - 请求配置 { method, params, data, headers, timeout, ... } * @returns {Promise<any>} */ async request(url, options = {}) { const { method = 'GET', params, data, headers = {}, timeout = this.defaultOptions.timeout, ...rest } = options; const upperMethod = method.toUpperCase(); // 1. 拼接完整 URL 和 Query 参数 const fullUrl = this._buildUrl(url, params); // 2. 合并 Headers (默认携带 application/json) const finalHeaders = { 'Content-Type': 'application/json', ...this.defaultOptions.headers, ...headers, }; // 3. 构建 Fetch 选项 (GET/HEAD 请求不允许携带 body) const fetchOptions = { method: upperMethod, headers: finalHeaders, ...rest, }; if (data && !['GET', 'HEAD'].includes(upperMethod)) { fetchOptions.body = typeof data === 'string' ? data : JSON.stringify(data); } // 4. 超时控制 (使用 AbortController) const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), timeout); fetchOptions.signal = controller.signal; try { const response = await fetch(fullUrl, fetchOptions); clearTimeout(timeoutId); // 5. 校验 HTTP 状态码 (fetch 只有在网络故障时才会 reject,4xx/5xx 需要手动判断) if (!response.ok) { throw new Error(`HTTP Error: ${response.status} ${response.statusText}`); } // 6. 智能解析响应体 const contentType = response.headers.get('content-type'); if (contentType && contentType.includes('application/json')) { return await response.json(); } return await response.text(); } catch (error) { clearTimeout(timeoutId); if (error.name === 'AbortError') { throw new Error('Request Timeout'); } // 这里可以统一接入全局错误提示 (如 Toast.error(error.message)) throw error; } } /** * 辅助方法:拼接 URL 和 Query 参数 */ _buildUrl(url, params) { const targetUrl = url.startsWith('http') ? url : this.baseURL + url; if (!params) return targetUrl; const searchParams = new URLSearchParams(); Object.keys(params).forEach(key => { if (params[key] !== undefined && params[key] !== null) { searchParams.append(key, params[key]); } }); const queryString = searchParams.toString(); return queryString ? `${targetUrl}?${queryString}` : targetUrl; } // ================= 快捷方法 ================= get(url, params = {}, options = {}) { return this.request(url, { ...options, method: 'GET', params }); } post(url, data = {}, options = {}) { return this.request(url, { ...options, method: 'POST', data }); } put(url, data = {}, options = {}) { return this.request(url, { ...options, method: 'PUT', data }); } delete(url, params = {}, options = {}) { // RESTful 风格中,DELETE 有时带 params,有时带 data,这里默认支持 params return this.request(url, { ...options, method: 'DELETE', params }); } } // 导出 (支持 ES Modules 和 CommonJS) export default Http; // module.exports = Http; // 如果是 CommonJS 环境,取消此行注释
1. 基础实例化与调用
// 实例化,配置全局基础 URL 和默认 Header (例如 Token) const api = new Http('https://api.example.com/v1', { headers: { 'Authorization': 'Bearer YOUR_TOKEN_HERE' }, timeout: 8000 // 全局默认 8 秒超时 }); // GET 请求 (自动拼接 ?id=123&type=user) api.get('/users', { id: 123, type: 'user' }) .then(res => console.log('用户数据:', res)) .catch(err => console.error('请求失败:', err.message)); // POST 请求 (自动 JSON.stringify body) api.post('/users', { name: '张三', age: 25 }) .then(res => console.log('创建成功:', res)) .catch(err => console.error('请求失败:', err.message));
2. 临时覆盖默认配置
// 某次请求需要不同的 Header 或更长的超时时间 api.post( '/upload', { file: 'data' }, { timeout: 30000, // 此次请求 30 秒超时 headers: { 'Content-Type': 'multipart/form-data' } // 覆盖默认 Content-Type } );
3. 在 HTML 中直接引入使用 (无需构建工具)
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>Fetch 封装示例</title> <!-- 引入 http.js文件 --> <script src="./http.js"></script> </head> <body> <button id="btn">获取数据</button> <script type="module"> // 假设 Http 类已在当前作用域定义 const api = new Http('https://jsonplaceholder.typicode.com'); document.getElementById('btn').addEventListener('click', async () => { try { const data = await api.get('/posts', { _limit: 1 }); console.log('获取成功:', data); alert(JSON.stringify(data)); } catch (error) { console.error('获取失败:', error.message); alert('请求失败: ' + error.message); } }); </script> </body> </html>
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/24 11:26:36

豆包AI平台:MoE架构与情境感知技术解析

1. 豆包平台全景解析2026年的智能助手领域正在经历一场范式转移。作为字节跳动旗下最新一代AI智能体平台&#xff0c;豆包正在重新定义人机交互的边界。这个全场景解决方案最令人惊艳之处在于&#xff0c;它彻底打破了传统语音助手"一问一答"的机械模式&#xff0c;转…

作者头像 李华
网站建设 2026/7/24 11:23:41

VC++实现图像降噪:均值与中值滤波算法详解与实战

1. 项目概述&#xff1a;从理论到实践的图像降噪之旅在数字图像处理的世界里&#xff0c;噪声就像照片上不请自来的“访客”&#xff0c;无论是老式扫描仪的颗粒感&#xff0c;还是低光照环境下相机传感器的热噪声&#xff0c;都会破坏图像的纯净度和细节表现。对于刚接触VC和图…

作者头像 李华
网站建设 2026/7/24 11:23:25

TDA2x引脚复用配置实战:从原理到代码的嵌入式硬件设计指南

1. 项目概述与引脚复用核心价值在嵌入式系统硬件设计领域&#xff0c;尤其是面对像德州仪器TDA2x系列这样功能高度集成的复杂SoC时&#xff0c;一个绕不开的核心议题就是引脚复用。我刚接触这类芯片时&#xff0c;也曾被其动辄数百页的引脚配置表搞得晕头转向&#xff0c;但一旦…

作者头像 李华
网站建设 2026/7/24 11:23:19

cursor uv sync直接卡死cursor解析

一、问题晚上uv sync 评估依赖的时候&#xff0c;总是卡死cursor&#xff0c; 早上依旧如此&#xff0c;现在准备一步步排查问题了。二、排查1. 网络测试&#xff0c;PyPI 连接ping pypi.orgping命令成功发送了多个数据包&#xff08;icmp_seq从4到16&#xff09;&#xff0c;并…

作者头像 李华