1. 前端请求技术全景图
现代前端开发中,数据交互早已不再是简单的表单提交。从早期的XMLHttpRequest到如今的Fetch API,从简单的GET请求到复杂的GraphQL查询,前端工程师需要掌握一整套网络通信技术栈。我在实际项目中踩过不少坑,比如用jQuery的$.ajax处理二进制文件上传时遇到的缓存问题,或是Fetch API默认不携带cookie导致的权限校验失败。这些经验让我深刻理解:不同的请求方法适用于不同的场景,选错工具可能带来灾难性后果。
浏览器环境下的请求发送主要分为三个技术世代:
- 上古时期的XMLHttpRequest(XHR)
- jQuery时代的$.ajax封装
- 现代Web标准的Fetch API
此外还有基于Promise的axios这类第三方库,以及WebSocket、Server-Sent Events等特殊协议。我们先从最基础的XHR开始拆解,这是理解其他所有方法的基础。
2. XMLHttpRequest 深度解析
2.1 基础使用模式
XHR虽然老旧,但所有现代API都在底层与之兼容。下面是一个带完整错误处理的典型XHR示例:
const xhr = new XMLHttpRequest(); xhr.open('GET', '/api/data', true); // 异步请求 xhr.timeout = 5000; // 5秒超时 xhr.withCredentials = true; // 携带凭据 xhr.onload = function() { if (xhr.status >= 200 && xhr.status < 300) { console.log(JSON.parse(xhr.responseText)); } else { console.error(`请求失败: ${xhr.statusText}`); } }; xhr.onerror = function() { console.error('网络错误'); }; xhr.ontimeout = function() { console.error('请求超时'); }; xhr.send();关键点解析:
open()方法的第三个参数决定异步模式(建议永远为true)withCredentials控制跨域请求是否携带cookie- 必须手动处理HTTP状态码,服务器返回404也会触发onload
2.2 高级功能与坑点
文件上传进度监控:
const uploadProgress = e => { if (e.lengthComputable) { const percent = Math.round((e.loaded / e.total) * 100); console.log(`上传进度: ${percent}%`); } }; xhr.upload.addEventListener('progress', uploadProgress);二进制数据处理:
xhr.responseType = 'arraybuffer'; // 也可以是 'blob'/'json' xhr.onload = () => { const arrayBuffer = xhr.response; // 处理二进制数据... };踩坑记录:在IE9中设置responseType必须在open()之后send()之前,否则会抛出异常。这种浏览器兼容性问题正是催生Fetch API的原因之一。
3. Fetch API 现代实践
3.1 基本用法与差异
Fetch API的核心理念是基于Promise,下面是一个带错误处理的完整示例:
fetch('/api/data', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Custom-Header': 'value' }, body: JSON.stringify({key: 'value'}), credentials: 'include', // 相当于XHR的withCredentials mode: 'cors' // 跨域模式 }) .then(response => { if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response.json(); }) .catch(error => { console.error('请求失败:', error); });与XHR的主要区别:
- 默认不携带cookie,需要显式设置credentials
- 服务器返回404/500时不会reject Promise
- 没有原生超时控制(需结合AbortController)
3.2 高级功能实现
请求中断:
const controller = new AbortController(); setTimeout(() => controller.abort(), 5000); fetch('/api', { signal: controller.signal }).catch(e => { if (e.name === 'AbortError') { console.log('请求被中止'); } });流式数据处理:
const response = await fetch('/large-file'); const reader = response.body.getReader(); while (true) { const {done, value} = await reader.read(); if (done) break; console.log('收到数据块:', value); }性能优化技巧:
- 对于JSON API,使用
res.json()比res.text()+JSON.parse()更快 - 设置
Accept-Encoding: gzip头可减少传输体积 - 复用TCP连接:保持同源请求使用相同fetch实例
4. 第三方库实战对比
4.1 axios核心优势
axios.interceptors.request.use(config => { config.headers['X-Request-Id'] = uuidv4(); return config; }); axios.get('/api', { params: { page: 1 }, timeout: 3000, transformResponse: [ data => JSON.parse(data).results ] });特色功能:
- 请求/响应拦截器
- 自动转换JSON数据
- 客户端XSRF防护
- 并发请求控制
- 取消令牌系统
4.2 特殊场景解决方案
GraphQL请求:
import { GraphQLClient } from 'graphql-request'; const client = new GraphQLClient('/graphql'); const query = `{ user(id: "1") { name posts { title } } }`; client.request(query).then(data => console.log(data));WebSocket实时通信:
const socket = new WebSocket('wss://example.com'); socket.onmessage = event => { console.log('收到消息:', event.data); }; socket.send(JSON.stringify({action: 'subscribe'}));5. 性能优化与安全实践
5.1 请求合并策略
批量请求示例:
// 单个请求:/api/users/1 // 批量请求:/api/users?id=1,2,3,4 async function getUsers(ids) { const response = await fetch(`/api/users?id=${ids.join(',')}`); return response.json(); }请求去重方案:
const pendingRequests = new Map(); async function cachedFetch(url) { if (pendingRequests.has(url)) { return pendingRequests.get(url); } const promise = fetch(url).then(res => res.json()); pendingRequests.set(url, promise); try { return await promise; } finally { pendingRequests.delete(url); } }5.2 安全防护要点
CSRF防护:
- 使用SameSite Cookie属性
- 添加
X-CSRF-Token请求头 - 实现双重提交Cookie模式
敏感数据保护:
// 错误示例 - 敏感信息出现在URL中 fetch(`/api/user/delete?id=123`); // 正确做法 - 使用POST+body fetch('/api/user/delete', { method: 'POST', body: JSON.stringify({id: 123}) });速率限制处理:
fetch('/api', { headers: { 'X-RateLimit-Limit': 100, 'X-RateLimit-Remaining': 99 } }).catch(async err => { if (err.response?.status === 429) { const retryAfter = err.response.headers.get('Retry-After'); await new Promise(r => setTimeout(r, retryAfter * 1000)); return fetch(...arguments); } throw err; });
6. 实战问题排查指南
6.1 跨域问题解决方案
完整CORS配置示例:
// 后端设置示例(Node.js) app.use((req, res, next) => { res.header('Access-Control-Allow-Origin', 'https://yourdomain.com'); res.header('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE'); res.header('Access-Control-Allow-Headers', 'Content-Type,Authorization'); res.header('Access-Control-Allow-Credentials', 'true'); if (req.method === 'OPTIONS') { return res.sendStatus(204); } next(); });常见错误排查:
- 预检请求失败:检查OPTIONS方法是否处理
- 凭证模式问题:确保
credentials: 'include'与Access-Control-Allow-Credentials: true配对 - 响应头缺失:确认服务器返回了
Access-Control-Expose-Headers
6.2 缓存问题处理
强制缓存失效策略:
fetch(`/api/data?t=${Date.now()}`, { headers: { 'Cache-Control': 'no-cache', 'Pragma': 'no-cache' } });ETag验证实践:
const response = await fetch('/api/data'); const etag = response.headers.get('ETag'); // 后续请求携带ETag const updated = await fetch('/api/data', { headers: { 'If-None-Match': etag } }); if (updated.status === 304) { // 使用缓存数据 }7. 现代浏览器API进阶
7.1 Beacon API 页面关闭时上报数据
// 适用于页面卸载前的数据上报 window.addEventListener('unload', () => { const data = JSON.stringify({event: 'page_close'}); navigator.sendBeacon('/analytics', data); });特点:
- 异步非阻塞
- 无视页面关闭过程
- 自动处理Content-Type
7.2 Server-Sent Events 实时数据流
const eventSource = new EventSource('/updates'); eventSource.onmessage = e => { console.log('新消息:', e.data); }; eventSource.addEventListener('stockUpdate', e => { const data = JSON.parse(e.data); updateChart(data); });与WebSocket的区别:
- 单向通信(服务端→客户端)
- 基于HTTP协议
- 自动重连机制
- 内置事件类型支持
8. 测试与调试技巧
8.1 请求Mock方案
浏览器原生Mock:
// 在开发者工具Console中执行 await fetch('/mock-api', { method: 'POST', body: JSON.stringify({mock: true}) }); // 然后在Network面板找到该请求 // 右键选择"Save as HAR with content"MSW库使用示例:
import { setupWorker, rest } from 'msw'; const worker = setupWorker( rest.get('/api/user', (req, res, ctx) => { return res( ctx.delay(150), ctx.json({ name: 'Mock User' }) ); }) ); worker.start();8.2 性能分析指标
关键监控点:
- TTFB (Time To First Byte)
- 请求排队时间
- 内容下载耗时
- 请求瀑布流分析
Chrome DevTools实战:
- 打开Network面板
- 勾选"Capture screenshots"
- 设置CPU throttling为4x slowdown
- 记录页面加载过程
- 分析关键请求链
9. 工程化最佳实践
9.1 请求层封装设计
// api-client.js class ApiClient { constructor(baseURL) { this.baseURL = baseURL; this.interceptors = []; } async request(path, options = {}) { let url = `${this.baseURL}${path}`; let config = { headers: {'Content-Type': 'application/json'}, ...options }; // 执行拦截器 for (const interceptor of this.interceptors) { ({url, config} = await interceptor(url, config)); } const response = await fetch(url, config); if (!response.ok) { throw new ApiError(response.status, await response.text()); } return response.json(); } }9.2 TypeScript集成方案
interface ApiResponse<T> { data: T; error?: string; } async function fetchUser(id: string): Promise<ApiResponse<User>> { const response = await fetch(`/api/users/${id}`); return response.json(); } // 使用示例 const {data, error} = await fetchUser('123'); if (error) { console.error(error); } else { console.log(data.name); }类型安全带来的优势:
- 自动补全API端点
- 响应数据结构验证
- 错误处理流程标准化
- 文档生成基础
10. 未来趋势与备选方案
10.1 WebTransport新协议
const transport = new WebTransport('https://example.com:4999/'); await transport.ready; // 等待连接建立 const stream = await transport.createBidirectionalStream(); const writer = stream.writable.getWriter(); await writer.write(new TextEncoder().encode('Hello'));特性对比:
- 基于QUIC协议
- 多路复用支持
- 低延迟传输
- 无序交付能力
10.2 WebRTC数据通道
const pc = new RTCPeerConnection(); const dc = pc.createDataChannel('chat'); dc.onmessage = e => { console.log('收到消息:', e.data); }; dc.send('Hello peer!');适用场景:
- P2P文件传输
- 实时游戏数据同步
- 去中心化应用通信
- 高延迟容忍场景