Axios
axios 是一个基于 Promise 的 HTTP 客户端,可用于浏览器和 Node.js。
Installation
bash
npm i axiosbash
yarn add axiosbash
pnpm add axiosbash
bun add axiosQuick Start
GET
ts
import axios from 'axios'
type User = { id: number; name: string }
const { data } = await axios.get<User>('https://example.com/api/users/1')POST
ts
import axios from 'axios'
type CreateUserBody = { name: string }
type CreateUserResp = { id: number }
const body: CreateUserBody = { name: 'Alice' }
const { data } = await axios.post<CreateUserResp>(
'https://example.com/api/users',
body,
)Instance(推荐)
ts
import axios from 'axios'
export const api = axios.create({
baseURL: 'https://example.com/api',
timeout: 10_000,
headers: {
'Content-Type': 'application/json',
},
})常用 defaults
ts
api.defaults.baseURL = 'https://example.com/api'
api.defaults.timeout = 10_000
api.defaults.headers.common.Authorization = 'Bearer token'Request Config 速查
ts
await api.request({
url: '/users',
method: 'GET',
params: { page: 1, pageSize: 20 },
headers: { 'X-Trace-Id': 'xxx' },
timeout: 10_000,
responseType: 'json',
withCredentials: false,
})Response 结构
ts
import type { AxiosResponse } from 'axios'
type User = { id: number; name: string }
const res: AxiosResponse<User> = await api.get<User>('/users/1')
const user: User = res.dataInterceptors
请求拦截:统一加 Token
ts
import axios from 'axios'
const requestInterceptorId = api.interceptors.request.use((config) => {
const token = localStorage.getItem('token') ?? ''
if (token) {
config.headers = config.headers ?? {}
config.headers.Authorization = `Bearer ${token}`
}
return config
})
api.interceptors.request.eject(requestInterceptorId)响应拦截:统一取 data
ts
api.interceptors.response.use((response) => response.data)如果你做了 “response.data 解包”,那么后续返回值不再是 AxiosResponse,而是你的业务数据类型。
错误处理
ts
import axios from 'axios'
try {
await api.get('/users/1')
} catch (err) {
if (axios.isAxiosError(err)) {
const status = err.response?.status
const data = err.response?.data
const message = err.message
} else {
throw err
}
}取消请求(AbortController)
ts
const controller = new AbortController()
const p = api.get('/users', { signal: controller.signal })
controller.abort()
await p上传文件(FormData)
ts
const form = new FormData()
form.append('file', file)
form.append('name', 'avatar')
await api.post('/upload', form, {
onUploadProgress: (e) => {
const total = e.total ?? 0
const percent = total ? Math.round((e.loaded / total) * 100) : 0
},
})并发请求
ts
const [a, b] = await Promise.all([api.get('/a'), api.get('/b')])请求重试
网络请求可能因为以下原因失败:
- 网络中断
- 服务器临时故障(503、504等)
- 请求超时
- 连接断开
插件
基础使用
js
import axios from 'axios'
import axiosRetry from 'axios-retry'
// 创建 axios 实例
const apiClient = axios.create({
baseURL: 'https://api.example.com',
timeout: 10000,
})
// 配置重试
axiosRetry(apiClient, {
retries: 3, // 重试次数
retryDelay: axiosRetry.exponentialDelay, // 指数退避延迟
retryCondition: (error) => {
// 默认只对网络错误和 5xx 状态码重试
return axiosRetry.isNetworkOrIdempotentRequestError(error)
},
shouldResetTimeout: false, // 重试时是否重置超时计时器
})
export default apiClient在请求中使用
js
import apiClient from './api'
// 自动重试(默认配置)
const response = await apiClient.get('/users')
// 单个请求自定义重试配置
const response2 = await apiClient.get('/users', {
'axios-retry': {
retries: 5, // 覆盖全局配置
retryDelay: (count) => count * 2000,
retryCondition: (error) => error.response?.status === 503,
},
})拦截器
不引入依赖,用拦截器自己实现
use
首先,了解一下响应拦截器中返回的response,它是一个AxiosResponse对象,包含了响应相关的全部元数据
ts
interface AxiosResponse<T = any> {
data: T // 服务器返回的实际数据(JSON 解析后的对象)
status: number // HTTP 状态码,如 200, 404, 500
statusText: string // HTTP 状态文本,如 "OK", "Not Found"
headers: any // 响应头(对象)
config: AxiosRequestConfig // 本次请求的配置
request: XMLHttpRequest // 或 浏览器请求对象(在 Node.js 中可能是 http.ClientRequest)
}注意后端返回的数据在response.data中,而不是response本身,这对后面对request做二次提取很重要
封装好的request函数,放在/utils/request.ts中
ts
import axios from 'axios'
import type {
AxiosInstance,
AxiosRequestConfig,
InternalAxiosRequestConfig,
AxiosError,
} from 'axios'
// 1. 根据后端约定的标准返回结构定义接口
export interface ApiResponse<T = any> {
code: number
msg: string
data: T
}
// 2. 自定义请求扩展配置(支持控制是否显示默认报错、跳过 Token 等)
export interface RequestConfig<D = any> extends AxiosRequestConfig<D> {
showError?: boolean // 是否自动弹出错误提示,默认 true
skipToken?: boolean // 是否跳过自动携带 Token,默认 false
}
// 3. 定义控制取消请求的 Map 容器
const pendingMap = new Map<string, AbortController>()
/**
* 生成请求的唯一 key
*/
function getPendingKey(config: AxiosRequestConfig): string {
return [
config.method,
config.url,
JSON.stringify(config.params),
JSON.stringify(config.data),
].join('&')
}
/**
* 添加重复请求控制
*/
function addPending(config: InternalAxiosRequestConfig) {
removePending(config)
const key = getPendingKey(config)
const controller = new AbortController()
config.signal = config.signal || controller.signal
pendingMap.set(key, controller)
}
/**
* 移除并取消重复请求
*/
function removePending(config: AxiosRequestConfig) {
const key = getPendingKey(config)
if (pendingMap.has(key)) {
const controller = pendingMap.get(key)
controller?.abort()
pendingMap.delete(key)
}
}
// 4. 创建 Axios 实例
const service: AxiosInstance = axios.create({
timeout: 10000,
headers: {
'Content-Type': 'application/json;charset=utf-8',
},
})
// 5. 请求拦截器
service.interceptors.request.use(
(config: InternalAxiosRequestConfig & RequestConfig) => {
// 移除上一次尚未完成的重复请求
removePending(config)
// 注册当前请求
addPending(config)
// 自动注入 Token(可根据实际情况替换 storage/pinia 获取逻辑)
if (!config.skipToken) {
const token = localStorage.getItem('ACCESS_TOKEN')
if (token && config.headers) {
config.headers.Authorization = `Bearer ${token}`
}
}
return config
},
(error: AxiosError) => {
return Promise.reject(error)
},
)
// 6. 响应拦截器
service.interceptors.response.use(
(response) => {
const { config } = response
// 请求成功,从 pendingMap 中移除
removePending(config)
const res = response.data
// 假设 200 或 0 代表业务成功(根据项目后端约定调整)
if (res.code === 200 || res.code === 0) {
return response
}
// --- 业务层面错误处理 (code !== 200) ---
const customConfig = config as RequestConfig
if (customConfig.showError !== false) {
// TODO: 替换为你项目UI组件库的 Message / Toast 提示组件
console.error(`[API Error]: ${res.msg || '系统异常'}`)
}
// 特定业务 Code 拦截(如 Token 过期/无效)
if (res.code === 401) {
// 触发登出或跳转登录页
localStorage.removeItem('ACCESS_TOKEN')
window.location.href = '/login'
}
return Promise.reject(new Error(res.msg || 'Error'))
},
(error: AxiosError) => {
if (axios.isCancel(error)) {
console.log('重复请求已取消:', error.message)
return Promise.reject(error)
}
// 移除失败请求
if (error.config) {
removePending(error.config)
}
// HTTP 状态码非 2xx 的情况
let message = '网络异常,请稍后再试'
if (error.response) {
switch (error.response.status) {
case 400:
message = '请求参数错误'
break
case 401:
message = '未授权,请重新登录'
break
case 403:
message = '拒绝访问'
break
case 404:
message = '请求地址不存在'
break
case 500:
message = '服务器内部错误'
break
default:
message = `连接错误 ${error.response.status}`
}
} else if (error.message.includes('timeout')) {
message = '网络请求超时'
}
console.error(`[HTTP Error]: ${message}`)
return Promise.reject(error)
},
)
/**
* 7. 核心通用请求函数封装(利用泛型重写返回类型)
*/
export const request = <T = any>(config: RequestConfig): Promise<T> => {
return service.request<ApiResponse<T>>(config).then((res) => res.data.data)
}
export default request