一、Axios 是什么?
Axios 是一个基于 Promise 的 HTTP 客户端,可以同时在 浏览器 和 Node.js 环境中使用。
// 最基础的用法
axios.get('/api/users')
.then(response => console.log(response.data))
.catch(error => console.error(error));
二、Axios 的安装与引入
2.1 安装方式
# npm 安装(推荐)
npm install axios
# yarn 安装
yarn add axios
# CDN 引入
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
2.2 引入方式
// ES Module(现代项目)
import axios from 'axios';
// CommonJS(Node.js)
const axios = require('axios');
// 浏览器中直接使用
// <script> 标签引入后,全局有 axios 对象
三、Axios API
3.1 请求方法
// ===== 基础方法 =====
// GET 请求
axios.get('/api/users', {
params: { page: 1, limit: 10 }
});
// POST 请求
axios.post('/api/users', {
name: '张三',
email: 'zhangsan@example.com'
});
// PUT 请求(全量更新)
axios.put('/api/users/1', {
name: '张三更新',
email: 'newemail@example.com'
});
// PATCH 请求(部分更新)
axios.patch('/api/users/1', {
name: '张三'
});
// DELETE 请求
axios.delete('/api/users/1');
// HEAD 请求
axios.head('/api/users/1');
// OPTIONS 请求
axios.options('/api/users/1');
// ===== 通用请求方法 =====
axios({
method: 'post',
url: '/api/users',
data: { name: '李四' }
});
// ===== 并发请求 =====
axios.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);
}));
// 或使用 Promise.all(更现代)
const [users, posts, comments] = await Promise.all([
axios.get('/api/users'),
axios.get('/api/posts'),
axios.get('/api/comments')
]);
3.2 请求配置
const config = {
// ----- 基础配置 -----
url: '/api/users', // 请求 URL(必须)
method: 'get', // 请求方法(默认 get)
baseURL: 'https://api.example.com', // 基础 URL
// ----- 数据配置 -----
data: { // POST/PUT/PATCH 的请求体
name: '张三'
},
params: { // URL 查询参数(GET 用)
page: 1,
limit: 10
},
paramsSerializer: (params) => { // 自定义参数序列化
return JSON.stringify(params);
},
// ----- 头部配置 -----
headers: { // 自定义请求头
'Content-Type': 'application/json',
'Authorization': 'Bearer token'
},
// ----- 响应配置 -----
responseType: 'json', // 响应类型:json/blob/stream/text/arraybuffer
responseEncoding: 'utf8', // 响应编码
transformResponse: [(data) => { // 转换响应数据
return JSON.parse(data);
}],
// ----- 超时与取消 -----
timeout: 5000, // 超时时间(毫秒)
timeoutErrorMessage: '请求超时', // 超时错误信息
cancelToken: new axios.CancelToken(c => { // 取消令牌
cancelFn = c;
}),
// ----- 跨域与凭证 -----
withCredentials: true, // 跨域携带 Cookie
xsrfCookieName: 'XSRF-TOKEN', // CSRF Token 的 Cookie 名
xsrfHeaderName: 'X-XSRF-TOKEN', // CSRF Token 的 Header 名
// ----- 上传/下载进度 -----
onUploadProgress: (progressEvent) => {
const percent = Math.round((progressEvent.loaded * 100) / progressEvent.total);
console.log(`上传进度: ${percent}%`);
},
onDownloadProgress: (progressEvent) => {
const percent = Math.round((progressEvent.loaded * 100) / progressEvent.total);
console.log(`下载进度: ${percent}%`);
},
// ----- 其他高级配置 -----
maxContentLength: 2000, // 响应体最大长度(字节)
maxBodyLength: 2000, // 请求体最大长度(字节)
maxRedirects: 5, // 最大重定向次数
socketPath: null, // Unix Socket 路径
httpAgent: new http.Agent(), // Node.js HTTP Agent
httpsAgent: new https.Agent(), // Node.js HTTPS Agent
proxy: { // 代理配置(Node.js)
host: '127.0.0.1',
port: 9000,
auth: { username: 'user', password: 'pass' }
},
decompress: true, // 自动解压响应数据
transitional: { // 过渡选项(处理废弃属性)
silentJSONParsing: true,
forcedJSONParsing: true,
clarifyTimeoutError: false
}
};
3.3 响应对象结构
const response = await axios.get('/api/users');
// 响应对象包含:
console.log(response.data); // 服务器返回的数据
console.log(response.status); // HTTP 状态码 (200)
console.log(response.statusText); // HTTP 状态文本 ('OK')
console.log(response.headers); // 响应头对象
console.log(response.config); // 本次请求的配置
console.log(response.request); // 底层请求对象 (XMLHttpRequest 或 http.ClientRequest)
四、拦截器
4.1 拦截器的基本使用
// ===== 请求拦截器 =====
axios.interceptors.request.use(
config => {
// 在请求发送之前做些什么
console.log('请求拦截器:', config);
// 统一添加 Token
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
// 统一添加时间戳(防缓存)
if (config.method === 'get') {
config.params = {
...config.params,
_t: Date.now()
};
}
return config;
},
error => {
// 对请求错误做些什么
return Promise.reject(error);
}
);
// ===== 响应拦截器 =====
axios.interceptors.response.use(
response => {
// 对响应数据做点什么
console.log('响应拦截器:', response);
// 统一处理后端返回的数据结构
if (response.data.code === 0) {
return response.data.data; // 只返回业务数据
} else {
return Promise.reject(new Error(response.data.message));
}
},
error => {
// 对响应错误做点什么
console.error('响应错误:', error);
// 统一处理 401 未授权
if (error.response?.status === 401) {
localStorage.removeItem('token');
window.location.href = '/login';
}
// 统一错误提示
if (error.response) {
// 服务器返回了错误状态码
const message = error.response.data?.message || '请求失败';
showToast(message);
} else if (error.request) {
// 请求已发出但没有收到响应
showToast('网络异常,请检查网络连接');
} else {
// 请求配置出错
showToast('请求配置错误');
}
return Promise.reject(error);
}
);
4.2 移除拦截器
// 为拦截器命名
const requestInterceptor = axios.interceptors.request.use(config => config);
const responseInterceptor = axios.interceptors.response.use(response => response);
// 移除特定的拦截器
axios.interceptors.request.eject(requestInterceptor);
axios.interceptors.response.eject(responseInterceptor);
4.3 实例级别的拦截器
// 创建独立的实例
const apiClient = axios.create({
baseURL: 'https://api.example.com',
timeout: 10000
});
// 为这个实例单独添加拦截器
apiClient.interceptors.request.use(config => {
config.headers.Authorization = `Bearer ${localStorage.getItem('token')}`;
return config;
});
// 默认实例的拦截器不会影响 apiClient
4.4 拦截器的执行顺序
// 请求拦截器执行顺序:后添加的先执行(类似栈)
axios.interceptors.request.use(config => {
console.log('请求拦截器 1');
return config;
});
axios.interceptors.request.use(config => {
console.log('请求拦截器 2'); // 先输出
return config;
});
// 响应拦截器执行顺序:先添加的先执行
axios.interceptors.response.use(response => {
console.log('响应拦截器 1'); // 先输出
return response;
});
axios.interceptors.response.use(response => {
console.log('响应拦截器 2');
return response;
});
五、Axios 实例
创建独立的实例,适用于不同 API 服务或不同配置。
5.1 创建和配置实例
// 创建实例
const api = axios.create({
baseURL: 'https://api.example.com',
timeout: 5000,
headers: {
'Content-Type': 'application/json'
}
});
// 配置实例的默认值
api.defaults.baseURL = 'https://api.example.com';
api.defaults.headers.common['Authorization'] = 'Bearer token';
api.defaults.headers.post['Content-Type'] = 'application/json';
// 为实例添加拦截器
api.interceptors.request.use(config => {
config.headers.Authorization = `Bearer ${localStorage.getItem('token')}`;
return config;
});
// 使用实例
api.get('/users')
.then(res => console.log(res.data))
.catch(err => console.error(err));
5.2 多实例场景
// 主业务 API
const mainApi = axios.create({
baseURL: 'https://api.example.com',
timeout: 5000
});
// 第三方服务 API
const thirdPartyApi = axios.create({
baseURL: 'https://api.thirdparty.com',
timeout: 30000,
headers: {
'X-API-Key': 'thirdparty-key'
}
});
// 文件上传 API(特殊配置)
const uploadApi = axios.create({
baseURL: 'https://upload.example.com',
timeout: 60000,
headers: {
'Content-Type': 'multipart/form-data'
}
});
// 使用不同的实例
await mainApi.get('/users');
await thirdPartyApi.get('/weather');
await uploadApi.post('/file', formData);
六、错误处理详解
6.1 错误对象结构
axios.get('/api/notfound')
.catch(error => {
if (error.response) {
// 服务器响应了错误状态码(4xx/5xx)
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.request) {
// 请求发送了但没有收到响应
console.log(error.request);
} else {
// 请求配置出错
console.log(error.message);
}
console.log(error.config);
});
6.2 错误处理最佳实践
// 封装错误处理
class ApiError extends Error {
constructor(message, status, data) {
super(message);
this.status = status;
this.data = data;
this.name = 'ApiError';
}
}
// 全局错误处理函数
function handleApiError(error) {
if (error.response) {
const { status, data } = error.response;
switch (status) {
case 400:
throw new ApiError('请求参数错误', status, data);
case 401:
throw new ApiError('未授权,请重新登录', status, data);
case 403:
throw new ApiError('没有权限访问', status, data);
case 404:
throw new ApiError('请求资源不存在', status, data);
case 500:
throw new ApiError('服务器内部错误', status, data);
default:
throw new ApiError(data?.message || '请求失败', status, data);
}
} else if (error.request) {
throw new ApiError('网络异常,请检查网络连接', 0);
} else {
throw new ApiError(error.message || '请求配置错误', 0);
}
}
// 使用
async function fetchUsers() {
try {
const response = await axios.get('/api/users');
return response.data;
} catch (error) {
throw handleApiError(error);
}
}
七、高级特性
7.1 取消请求(CancelToken)
// ===== 方式1:使用 CancelToken.source =====
const source = axios.CancelToken.source();
axios.get('/api/slow-request', {
cancelToken: source.token
})
.then(response => console.log(response.data))
.catch(error => {
if (axios.isCancel(error)) {
console.log('请求已取消:', error.message);
} else {
console.error('其他错误:', error);
}
});
// 取消请求
source.cancel('用户取消了请求');
// ===== 方式2:使用 CancelToken 构造函数 =====
let cancel;
axios.get('/api/slow-request', {
cancelToken: new axios.CancelToken(c => {
cancel = c;
})
});
cancel('用户取消');
// ===== 实战:防抖搜索 =====
let cancelToken;
function search(keyword) {
// 取消上一次未完成的请求
if (cancelToken) {
cancelToken.cancel('取消旧请求');
}
cancelToken = axios.CancelToken.source();
axios.get('/api/search', {
params: { q: keyword },
cancelToken: cancelToken.token
})
.then(response => {
console.log('搜索结果:', response.data);
})
.catch(error => {
if (!axios.isCancel(error)) {
console.error('搜索失败:', error);
}
});
}
7.2 转换器(Transformer)
// ===== 请求转换 =====
axios.defaults.transformRequest = [(data, headers) => {
// 自动处理不同的数据格式
if (data instanceof FormData) {
return data;
}
if (data instanceof URLSearchParams) {
return data.toString();
}
// 默认 JSON
return JSON.stringify(data);
}];
// ===== 响应转换 =====
axios.defaults.transformResponse = [(data) => {
// 如果数据是字符串,尝试解析为 JSON
if (typeof data === 'string') {
try {
data = JSON.parse(data);
} catch (e) {
// 不是 JSON,保持原样
}
}
return data;
}];
7.3 适配器(Adapter)
Axios 支持在不同环境使用不同适配器:
// 浏览器默认使用 XMLHttpRequest
// Node.js 默认使用 http/https
// 自定义适配器(示例)
const customAdapter = (config) => {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(config.method, config.url, true);
xhr.onload = () => resolve({ /* response */ });
xhr.onerror = reject;
xhr.send(config.data);
});
};
// 使用自定义适配器
const instance = axios.create({
adapter: customAdapter
});
7.4 进度监控
// 上传进度(适合文件上传)
async function uploadFile(file) {
const formData = new FormData();
formData.append('file', file);
const response = await axios.post('/api/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data'
},
onUploadProgress: (progressEvent) => {
const percent = Math.round((progressEvent.loaded * 100) / progressEvent.total);
console.log(`上传进度: ${percent}%`);
// 更新进度条 UI
updateProgressBar(percent);
}
});
return response.data;
}
// 下载进度(适合大文件下载)
async function downloadLargeFile() {
const response = await axios.get('/api/large-file', {
responseType: 'blob',
onDownloadProgress: (progressEvent) => {
const percent = Math.round((progressEvent.loaded * 100) / progressEvent.total);
console.log(`下载进度: ${percent}%`);
}
});
// 触发浏览器下载
const url = URL.createObjectURL(response.data);
const a = document.createElement('a');
a.href = url;
a.download = 'large-file.pdf';
a.click();
URL.revokeObjectURL(url);
}
7.5 请求重试(使用 axios-retry)
npm install axios-retry
import axios from 'axios';
import axiosRetry from 'axios-retry';
// 配置重试
axiosRetry(axios, {
retries: 3, // 重试次数
retryDelay: (retryCount) => {
return retryCount * 1000; // 每次延迟递增 1 秒
},
retryCondition: (error) => {
// 只在特定错误时重试
return axiosRetry.isNetworkOrIdempotentRequestError(error) ||
error.response?.status === 503;
},
onRetry: (retryCount, error, requestConfig) => {
console.log(`第 ${retryCount} 次重试`, error.message);
}
});
// 现在会自动重试
axios.get('/api/unstable-endpoint')
.then(response => console.log(response.data))
.catch(error => console.error('所有重试都失败:', error));
八、性能优化与最佳实践
8.1 使用 CancelToken 防止内存泄漏
// React 中清理未完成的请求
useEffect(() => {
const source = axios.CancelToken.source();
axios.get('/api/data', { cancelToken: source.token })
.then(res => setData(res.data))
.catch(err => {
if (!axios.isCancel(err)) {
console.error(err);
}
});
// 组件卸载时取消所有请求
return () => source.cancel('组件卸载,取消请求');
}, []);
8.2 配置缓存策略
// 缓存 GET 请求
const cache = new Map();
async function cachedGet(url, params) {
const key = `${url}_${JSON.stringify(params)}`;
if (cache.has(key)) {
console.log('使用缓存数据');
return cache.get(key);
}
const response = await axios.get(url, { params });
cache.set(key, response.data);
return response.data;
}
8.3 请求防抖/节流
import { debounce } from 'lodash';
// 防抖搜索
const searchDebounced = debounce(async (keyword) => {
const result = await axios.get('/api/search', {
params: { q: keyword }
});
return result.data;
}, 300);
// 使用
input.addEventListener('input', (e) => {
searchDebounced(e.target.value).then(data => {
renderResults(data);
});
});
8.4 使用 HTTP/2 提升性能
// Node.js 中使用 HTTP/2
import http2 from 'http2';
const agent = new http2.Agent();
const instance = axios.create({
httpsAgent: agent,
httpAgent: agent
});