Appearance
HTTP 请求实战代码示例
原生 XMLHttpRequest
GET 请求
javascript
const xhr = new XMLHttpRequest()
xhr.open(
'GET',
'https://api.example.com/data?param1=value1¶m2=value2',
true
)
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
const response = JSON.parse(xhr.responseText)
console.log('GET 请求成功:', response)
} else {
console.error('GET 请求失败:', xhr.status)
}
}
}
xhr.send()POST 请求(JSON 数据)
javascript
const xhr = new XMLHttpRequest()
xhr.open('POST', 'https://api.example.com/data', true)
xhr.setRequestHeader('Content-Type', 'application/json')
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
const response = JSON.parse(xhr.responseText)
console.log('POST 请求成功:', response)
} else {
console.error('POST 请求失败:', xhr.status)
}
}
}
const data = { name: 'John', age: 30 }
xhr.send(JSON.stringify(data))Fetch API
GET 请求
javascript
fetch('https://api.example.com/data?param1=value1¶m2=value2')
.then(response => {
if (!response.ok) {
throw new Error('GET 请求失败')
}
return response.json()
})
.then(data => {
console.log('GET 请求成功:', data)
})
.catch(error => {
console.error('错误:', error)
})POST 请求(JSON 数据)
javascript
fetch('https://api.example.com/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ name: 'John', age: 30 })
})
.then(response => {
if (!response.ok) {
throw new Error('POST 请求失败')
}
return response.json()
})
.then(data => {
console.log('POST 请求成功:', data)
})
.catch(error => {
console.error('错误:', error)
})Axios
安装 Axios
bash
npm install axios基本配置
javascript
import axios from 'axios'
const instance = axios.create({
baseURL: 'https://api.example.com',
timeout: 10000,
headers: {
'Content-Type': 'application/json'
}
})GET 请求
javascript
instance
.get('/data', {
params: {
param1: 'value1',
param2: 'value2'
}
})
.then(response => {
console.log('GET 请求成功:', response.data)
})
.catch(error => {
console.error('GET 请求失败:', error)
})POST 请求
javascript
instance
.post('/data', {
name: 'John',
age: 30
})
.then(response => {
console.log('POST 请求成功:', response.data)
})
.catch(error => {
console.error('POST 请求失败:', error)
})请求拦截器
javascript
instance.interceptors.request.use(
config => {
// 添加 token
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
},
error => {
return Promise.reject(error)
}
)响应拦截器
javascript
instance.interceptors.response.use(
response => {
return response
},
error => {
if (error.response && error.response.status === 401) {
// Token 过期,跳转到登录页
window.location.href = '/login'
}
return Promise.reject(error)
}
)XMLHttpRequest vs Fetch vs Axios 对比
一、概述
| 特性 | XMLHttpRequest | Fetch API | Axios |
|---|---|---|---|
| 类型 | 原生 API | 原生 API | 第三方库 |
| 发布时间 | 2000年左右 | 2015年 (ES6) | 2014年 |
| Promise 支持 | ❌ 不支持 | ✅ 原生支持 | ✅ 原生支持 |
| 浏览器兼容性 | ✅ 最广泛 | ⚠️ 较新浏览器 | ✅ 广泛(需 polyfill) |
| 学习曲线 | 中等 | 简单 | 简单 |
二、XMLHttpRequest 详解
优点
浏览器兼容性极佳
- 支持所有主流浏览器,包括 IE6+
- 无需任何 polyfill 或额外库
功能全面
- 支持同步和异步请求
- 可以获取请求进度(上传/下载)
- 支持请求中断(abort)
- 可以设置超时时间
细粒度控制
javascriptconst xhr = new XMLHttpRequest() // 上传进度监听 xhr.upload.onprogress = function (e) { if (e.lengthComputable) { const percent = (e.loaded / e.total) * 100 console.log(`上传进度: ${percent}%`) } } // 下载进度监听 xhr.onprogress = function (e) { if (e.lengthComputable) { const percent = (e.loaded / e.total) * 100 console.log(`下载进度: ${percent}%`) } } // 超时设置 xhr.timeout = 5000 xhr.ontimeout = function () { console.error('请求超时') } // 请求中断 xhr.abort()
缺点
回调地狱
javascript// 典型的回调嵌套 xhr.onreadystatechange = function () { if (xhr.readyState === 4) { if (xhr.status === 200) { // 嵌套下一个请求 xhr2.onreadystatechange = function () { if (xhr2.readyState === 4) { // 继续嵌套... } } } } }API 设计老旧
- 基于事件的模式不够直观
- 代码冗长,可读性差
- 需要手动解析 JSON
错误处理复杂
javascriptxhr.onreadystatechange = function () { if (xhr.readyState === 4) { // 需要手动判断状态码 if (xhr.status >= 200 && xhr.status < 300) { // 成功 } else { // 失败,但 404、500 等都会进入这里 } } }缺乏现代特性
- 不支持 Promise
- 不支持 async/await
- 没有 interceptors(拦截器)
三、Fetch API 详解
优点
现代化 API 设计
javascript// 简洁的 Promise 链式调用 fetch('https://api.example.com/data') .then(response => response.json()) .then(data => console.log(data)) .catch(error => console.error(error)) // 支持 async/await async function getData() { try { const response = await fetch('https://api.example.com/data') const data = await response.json() console.log(data) } catch (error) { console.error(error) } }标准化设计
- 基于 Promise,符合现代 JavaScript 规范
- Response 对象提供了丰富的处理方法
- 支持流式处理
模块化设计
javascript// Headers 对象 const headers = new Headers() headers.append('Content-Type', 'application/json') // Request 对象 const request = new Request('https://api.example.com/data', { method: 'POST', headers: headers, body: JSON.stringify({ name: 'John' }) }) // Response 对象 const response = await fetch(request)流式处理能力
javascript// 处理大文件流 const response = await fetch('https://example.com/large-file') const reader = response.body.getReader() while (true) { const { done, value } = await reader.read() if (done) break // 处理每个 chunk console.log('Received chunk:', value) }
缺点
错误处理设计争议
javascript// HTTP 错误(如 404、500)不会 reject fetch('https://api.example.com/not-exist') .then(response => { // 即使 404,也会进入这里 if (!response.ok) { throw new Error('请求失败') } return response.json() }) .catch(error => console.error(error)) // 只有网络错误才会 reject fetch('https://non-existent-domain.com').catch(error => console.error('网络错误:', error) )不支持超时(需要额外实现)
javascript// 使用 AbortController 实现超时 const controller = new AbortController() const timeoutId = setTimeout(() => controller.abort(), 5000) try { const response = await fetch('https://api.example.com/data', { signal: controller.signal }) clearTimeout(timeoutId) } catch (error) { if (error.name === 'AbortError') { console.error('请求超时') } }不支持进度监控
- 无法原生获取上传/下载进度
- 需要使用其他 API 配合实现
默认不携带 Cookie
javascript// 需要手动设置 fetch('https://api.example.com/data', { credentials: 'include' // 跨域携带 cookie })浏览器兼容性
- IE 完全不支持
- 部分功能需要较新的浏览器版本
四、Axios 详解
优点
开箱即用的完整功能
javascript// 自动 JSON 转换 axios.post('/api/data', { name: 'John' }).then(response => { // response.data 已经是解析后的对象 console.log(response.data) }) // 自动处理查询参数 axios.get('/api/data', { params: { id: 1, name: 'John' } // 自动序列化为 ?id=1&name=John })强大的拦截器系统
javascript// 请求拦截器 axios.interceptors.request.use( config => { // 统一添加 token config.headers.Authorization = `Bearer ${getToken()}` // 添加请求时间戳 config.metadata = { startTime: Date.now() } return config }, error => Promise.reject(error) ) // 响应拦截器 axios.interceptors.response.use( response => { // 计算请求耗时 const duration = Date.now() - response.config.metadata.startTime console.log(`请求耗时: ${duration}ms`) return response }, error => { // 统一错误处理 if (error.response) { switch (error.response.status) { case 401: // 跳转登录 break case 403: // 权限不足 break case 500: // 服务器错误 break } } return Promise.reject(error) } )请求取消
javascript// 使用 CancelToken(旧版) const source = axios.CancelToken.source() axios.get('/api/data', { cancelToken: source.token }) // 取消请求 source.cancel('请求被取消') // 使用 AbortController(新版,推荐) const controller = new AbortController() axios.get('/api/data', { signal: controller.signal }) controller.abort()并发请求
javascript// 同时发起多个请求 Promise.all([ axios.get('/api/users'), axios.get('/api/posts'), axios.get('/api/comments') ]).then( axios.spread((users, posts, comments) => { console.log('用户:', users.data) console.log('文章:', posts.data) console.log('评论:', comments.data) }) )超时设置简单
javascript// 全局超时 axios.defaults.timeout = 5000 // 单个请求超时 axios.get('/api/data', { timeout: 10000 })防御型 XSRF
javascriptaxios.defaults.xsrfCookieName = 'XSRF-TOKEN' axios.defaults.xsrfHeaderName = 'X-XSRF-TOKEN'进度监控
javascript// 上传进度 axios.post('/api/upload', formData, { onUploadProgress: progressEvent => { const percent = Math.round( (progressEvent.loaded * 100) / progressEvent.total ) console.log(`上传进度: ${percent}%`) } }) // 下载进度 axios.get('/api/download', { responseType: 'blob', onDownloadProgress: progressEvent => { const percent = Math.round( (progressEvent.loaded * 100) / progressEvent.total ) console.log(`下载进度: ${percent}%`) } })
缺点
额外依赖
- 需要安装第三方库
- 增加打包体积(约 13KB gzipped)
过度封装
- 某些场景下可能需要绕过 axios 的默认行为
- 错误对象结构与原生不同
版本兼容性
javascript// CancelToken 在新版本中已废弃 // 旧代码 const CancelToken = axios.CancelToken const source = CancelToken.source() // 新代码(推荐) const controller = new AbortController()
五、功能对比表
| 功能 | XMLHttpRequest | Fetch API | Axios |
|---|---|---|---|
| Promise 支持 | ❌ | ✅ | ✅ |
| async/await | ❌ | ✅ | ✅ |
| 自动 JSON 转换 | ❌ | ❌ | ✅ |
| 请求/响应拦截器 | ❌ | ❌ | ✅ |
| 请求取消 | ✅ abort() | ✅ AbortController | ✅ 多种方式 |
| 超时设置 | ✅ timeout 属性 | ⚠️ 需额外实现 | ✅ timeout 配置 |
| 上传进度 | ✅ | ❌ | ✅ |
| 下载进度 | ✅ | ❌ | ✅ |
| 流式处理 | ❌ | ✅ | ❌ |
| XSRF 防护 | ❌ | ❌ | ✅ |
| 并发请求 | ❌ | ✅ Promise.all | ✅ 专用 API |
| 同步请求 | ✅ | ❌ | ❌ |
| 浏览器兼容 | ✅ 最广 | ⚠️ 较新 | ✅ 广泛 |
| 包体积 | 0 | 0 | ~13KB |
六、使用场景建议
选择 XMLHttpRequest 的场景
javascript
// 1. 需要支持老旧浏览器(IE)
// 2. 需要同步请求(不推荐,但某些场景必须)
const xhr = new XMLHttpRequest()
xhr.open('GET', '/api/data', false) // 同步请求
xhr.send()
console.log(xhr.responseText)
// 3. 需要极其细粒度的控制
// 4. 项目无法引入第三方库选择 Fetch API 的场景
javascript
// 1. 现代浏览器环境
// 2. 简单的 CRUD 操作
// 3. 需要流式处理大文件
const response = await fetch('/api/large-data')
for await (const chunk of response.body) {
processChunk(chunk)
}
// 4. Service Worker 中使用
// 5. 追求零依赖选择 Axios 的场景
javascript
// 1. 企业级项目
// 2. 需要统一的请求/响应处理
// 3. 需要完善的错误处理机制
// 4. 需要请求重试、取消等高级功能
// 5. 需要上传/下载进度显示
// 6. 团队协作,需要统一的 API 调用规范
// 典型的企业级配置
const api = axios.create({
baseURL: process.env.API_URL,
timeout: 10000,
withCredentials: true
})
// 统一请求处理
api.interceptors.request.use(config => {
config.headers.Authorization = `Bearer ${getToken()}`
return config
})
// 统一响应处理
api.interceptors.response.use(
response => response.data,
error => {
handleGlobalError(error)
return Promise.reject(error)
}
)七、最佳实践
Fetch API 最佳实践封装
javascript
class HttpFetch {
constructor(baseURL, defaultOptions = {}) {
this.baseURL = baseURL
this.defaultOptions = {
headers: { 'Content-Type': 'application/json' },
...defaultOptions
}
}
async request(url, options = {}) {
const controller = new AbortController()
const timeoutId = setTimeout(
() => controller.abort(),
options.timeout || 10000
)
try {
const response = await fetch(`${this.baseURL}${url}`, {
...this.defaultOptions,
...options,
signal: controller.signal
})
clearTimeout(timeoutId)
if (!response.ok) {
const error = await response.json()
throw new Error(error.message || '请求失败')
}
return await response.json()
} catch (error) {
clearTimeout(timeoutId)
if (error.name === 'AbortError') {
throw new Error('请求超时')
}
throw error
}
}
get(url, params, options) {
const queryString = new URLSearchParams(params).toString()
return this.request(`${url}?${queryString}`, { ...options, method: 'GET' })
}
post(url, data, options) {
return this.request(url, {
...options,
method: 'POST',
body: JSON.stringify(data)
})
}
}
const http = new HttpFetch('https://api.example.com')Axios 最佳实践封装
javascript
import axios from 'axios'
const createHttpClient = (config = {}) => {
const instance = axios.create({
timeout: 10000,
...config
})
// 请求拦截器
instance.interceptors.request.use(
config => {
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
},
error => Promise.reject(error)
)
// 响应拦截器
instance.interceptors.response.use(
response => response.data,
error => {
const { response } = error
if (response) {
switch (response.status) {
case 401:
localStorage.removeItem('token')
window.location.href = '/login'
break
case 403:
console.error('权限不足')
break
case 500:
console.error('服务器错误')
break
}
}
return Promise.reject(error)
}
)
return instance
}
export const http = createHttpClient({
baseURL: process.env.VUE_APP_API_URL
})八、总结
| 维度 | 推荐选择 | 原因 |
|---|---|---|
| 现代项目 | Axios 或 Fetch | 代码简洁,Promise 支持 |
| 企业级应用 | Axios | 功能完善,生态成熟 |
| 老旧浏览器 | XMLHttpRequest | 兼容性最好 |
| 微型项目 | Fetch | 零依赖,体积小 |
| 需要进度监控 | Axios 或 XMLHttpRequest | Fetch 不支持 |
| 流式处理 | Fetch | 原生支持流 |
| 学习成本 | Fetch | API 简单直观 |
最终建议:
- 新项目推荐 Axios,功能完善,开发效率高
- 追求轻量级或现代特性,选择 Fetch
- 需要兼容 IE 或有特殊需求,使用 XMLHttpRequest