Axios 详解速览
1. Axios 概述
Axios 是一个基于 Promise 的 HTTP 客户端,可以用在浏览器和 Node.js 中。它提供了简单易用的 API 来发送 HTTP 请求。
核心特性:
- 基于 Promise:支持 async/await
- 跨平台:浏览器和 Node.js 都支持
- 拦截器:请求和响应拦截
- 转换器:请求和响应数据转换
- 取消请求:支持请求取消
- 自动转换:JSON 数据自动转换
- 客户端支持:支持 XSRF 保护
2. 基础安装和配置
2.1 安装
# npm
npm install axios
# yarn
yarn add axios
# CDN
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
2.2 基本使用
// 引入 axios
import axios from 'axios';
// GET 请求
axios.get('/api/users')
.then(response => {
console.log(response.data);
})
.catch(error => {
console.error('请求失败:', error);
});
// POST 请求
axios.post('/api/users', {
name: 'John Doe',
email: 'john@example.com'
})
.then(response => {
console.log('创建成功:', response.data);
})
.catch(error => {
console.error('创建失败:', error);
});
3. 请求方法详解
3.1 基本请求方法
// GET 请求
axios.get('/api/users')
.then(response => console.log(response.data));
// 带参数的 GET 请求
axios.get('/api/users', {
params: {
page: 1,
limit: 10
}
});
// POST 请求
axios.post('/api/users', {
name: 'John',
email: 'john@example.com'
});
// PUT 请求
axios.put('/api/users/1', {
name: 'John Updated',
email: 'john.updated@example.com'
});
// DELETE 请求
axios.delete('/api/users/1');
// PATCH 请求
axios.patch('/api/users/1', {
name: 'John Partial Update'
});
3.2 通用请求方法
// axios(config)
axios({
method: 'get',
url: '/api/users',
params: { id: 123 }
});
// axios(url[, config])
axios('/api/users', {
method: 'post',
data: { name: 'John' }
});
// 完整配置示例
axios({
method: 'post',
url: '/api/users',
data: {
firstName: 'John',
lastName: 'Doe'
},
params: {
timestamp: Date.now()
},
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token123'
},
timeout: 5000,
responseType: 'json'
});
4. 请求配置详解
4.1 完整配置选项
const config = {
// `url` 是用于请求的服务器 URL
url: '/api/users',
// `method` 是创建请求时使用的方法
method: 'get', // default
// `baseURL` 将自动加在 `url` 前面,除非 `url` 是一个绝对 URL
baseURL: 'https://api.example.com',
// `transformRequest` 允许在向服务器发送前,修改请求数据
transformRequest: [function (data, headers) {
// 对 data 进行任意转换处理
return data;
}],
// `transformResponse` 在传递给 then/catch 前,允许修改响应数据
transformResponse: [function (data) {
// 对 data 进行任意转换处理
return data;
}],
// `headers` 是即将被发送的自定义请求头
headers: {
'X-Requested-With': 'XMLHttpRequest'
},
// `params` 是即将与请求一起发送的 URL 参数
params: {
ID: 12345
},
// `paramsSerializer` 是一个负责 `params` 序列化的函数
paramsSerializer: function (params) {
return Qs.stringify(params, {arrayFormat: 'brackets'})
},
// `data` 是作为请求主体被发送的数据
data: {
firstName: 'John',
lastName: 'Doe'
},
// `timeout` 指定请求超时的毫秒数
timeout: 1000, // default is `0` (no timeout)
// `withCredentials` 表示跨域请求时是否需要使用凭证
withCredentials: false, // default
// `adapter` 允许自定义处理请求,以使测试更轻松
adapter: function (config) {
/* ... */
},
// `auth` 表示应该使用 HTTP 基础验证,并提供凭据
auth: {
username: 'janedoe',
password: 's00pers3cret'
},
// `responseType` 表示服务器响应的数据类型
responseType: 'json', // default
// `responseEncoding` 表示用于解码响应的编码
responseEncoding: 'utf8', // default
// `xsrfCookieName` 是 xsrf token 的值,被用作 cookie 的名称
xsrfCookieName: 'XSRF-TOKEN', // default
// `xsrfHeaderName` 是 xsrf token 的值,被用作 http 头名
xsrfHeaderName: 'X-XSRF-TOKEN', // default
// `onUploadProgress` 允许为上传处理进度事件
onUploadProgress: function (progressEvent) {
// Do whatever you want with the native progress event
},
// `onDownloadProgress` 允许为下载处理进度事件
onDownloadProgress: function (progressEvent) {
// Do whatever you want with the native progress event
},
// `maxContentLength` 定义允许的响应内容的最大尺寸
maxContentLength: 2000,
// `validateStatus` 定义对于给定的HTTP 响应状态码是 resolve 或 reject promise
validateStatus: function (status) {
return status >= 200 && status < 300; // default
},
// `maxRedirects` 定义在 node.js 中 follow 的最大重定向数目
maxRedirects: 5, // default
// `socketPath` 定义 UNIX Socket 的路径
socketPath: null, // default
// `httpAgent` 和 `httpsAgent` 分别在 node.js 中用于定义在执行 http 和 https 时使用的自定义代理
httpAgent: new http.Agent({ keepAlive: true }),
httpsAgent: new https.Agent({ keepAlive: true }),
// `proxy` 定义代理服务器的主机名称和端口
proxy: {
host: '127.0.0.1',
port: 9000,
auth: {
username: 'mikeymike',
password: 'rapunz3l'
}
},
// `cancelToken` 指定用于取消请求的 cancel token
cancelToken: new CancelToken(function (cancel) {
})
};
axios(config);
5. 响应结构
5.1 响应对象
axios.get('/api/users')
.then(response => {
// 响应数据
console.log(response.data);
// HTTP 状态码
console.log(response.status); // 200
// HTTP 状态信息
console.log(response.statusText); // 'OK'
// 响应头
console.log(response.headers);
// 请求配置
console.log(response.config);
// 请求对象
console.log(response.request);
});
5.2 错误处理
axios.get('/api/users')
.catch(error => {
if (error.response) {
// 请求已发出,但服务器响应的状态码不在 2xx 范围内
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. 拦截器
6.1 请求拦截器
// 添加请求拦截器
axios.interceptors.request.use(
function (config) {
// 在发送请求之前做些什么
console.log('发送请求:', config);
// 添加认证 token
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
// 添加时间戳
config.params = {
...config.params,
_t: Date.now()
};
return config;
},
function (error) {
// 对请求错误做些什么
return Promise.reject(error);
}
);
// 移除拦截器
const myInterceptor = axios.interceptors.request.use(() => {});
axios.interceptors.request.eject(myInterceptor);
6.2 响应拦截器
// 添加响应拦截器
axios.interceptors.response.use(
function (response) {
// 对响应数据做点什么
console.log('收到响应:', response);
// 统一处理数据格式
return response.data;
},
function (error) {
// 对响应错误做点什么
if (error.response) {
switch (error.response.status) {
case 401:
// 未授权,跳转到登录页
window.location.href = '/login';
break;
case 403:
// 禁止访问
console.error('禁止访问');
break;
case 404:
// 资源不存在
console.error('资源不存在');
break;
case 500:
// 服务器错误
console.error('服务器错误');
break;
default:
console.error('请求失败');
}
}
return Promise.reject(error);
}
);
6.3 自定义拦截器实例
// 创建实例并添加拦截器
const apiClient = axios.create({
baseURL: 'https://api.example.com',
timeout: 10000
});
// 为实例添加拦截器
apiClient.interceptors.request.use(
config => {
// 实例特定的请求处理
config.headers['X-App-Version'] = '1.0.0';
return config;
},
error => Promise.reject(error)
);
apiClient.interceptors.response.use(
response => response,
error => {
// 实例特定的错误处理
console.error('API 客户端错误:', error);
return Promise.reject(error);
}
);
7. 取消请求
7.1 使用 CancelToken
// 使用 CancelToken.source 工厂方法创建 cancel token
const source = axios.CancelToken.source();
axios.get('/api/users', {
cancelToken: source.token
}).catch(function (thrown) {
if (axios.isCancel(thrown)) {
console.log('请求已取消:', thrown.message);
} else {
// 处理错误
console.error('请求失败:', thrown);
}
});
// 取消请求
source.cancel('用户取消了请求');
7.2 使用 executor 函数创建 CancelToken
let cancel;
axios.get('/api/users', {
cancelToken: new axios.CancelToken(function executor(c) {
// executor 函数接收一个 cancel 函数作为参数
cancel = c;
})
});
// 取消请求
cancel('操作被用户取消');
7.3 实际应用:搜索自动完成
class SearchService {
constructor() {
this.cancelToken = null;
}
async search(query) {
// 取消之前的请求
if (this.cancelToken) {
this.cancelToken.cancel('新的搜索请求');
}
// 创建新的 cancel token
this.cancelToken = axios.CancelToken.source();
try {
const response = await axios.get('/api/search', {
params: { q: query },
cancelToken: this.cancelToken.token
});
return response.data;
} catch (error) {
if (axios.isCancel(error)) {
console.log('搜索请求被取消:', error.message);
return null;
}
throw error;
}
}
}
// 使用示例
const searchService = new SearchService();
// 用户输入时调用
document.getElementById('searchInput').addEventListener('input', async (e) => {
const query = e.target.value;
if (query.length > 2) {
const results = await searchService.search(query);
if (results) {
displayResults(results);
}
}
});
8. 实例创建和配置
8.1 创建实例
// 创建实例
const instance = axios.create({
baseURL: 'https://api.example.com',
timeout: 5000,
headers: {
'Content-Type': 'application/json',
'X-Custom-Header': 'foobar'
}
});
// 实例方法
instance.get('/users')
.then(response => console.log(response.data));
instance.post('/users', { name: 'John' })
.then(response => console.log(response.data));
8.2 实例配置覆盖
// 创建基础实例
const api = axios.create({
baseURL: 'https://api.example.com',
timeout: 10000,
headers: {
'Content-Type': 'application/json'
}
});
// 特定请求覆盖配置
api.get('/users', {
timeout: 5000, // 覆盖实例的超时设置
headers: {
'Authorization': 'Bearer token123' // 覆盖实例的头部
}
});
8.3 多个实例管理
// 不同服务的实例
const userService = axios.create({
baseURL: 'https://api.user-service.com',
headers: { 'X-Service': 'user' }
});
const productService = axios.create({
baseURL: 'https://api.product-service.com',
headers: { 'X-Service': 'product' }
});
const authService = axios.create({
baseURL: 'https://api.auth-service.com',
headers: { 'X-Service': 'auth' }
});
// 使用不同实例
userService.get('/profile');
productService.get('/products');
authService.post('/login', { username, password });
9. 请求和响应转换
9.1 请求转换器
// 全局请求转换器
axios.defaults.transformRequest = [
function (data, headers) {
// 处理 FormData
if (data instanceof FormData) {
return data;
}
// 处理普通对象
if (typeof data === 'object') {
headers['Content-Type'] = 'application/json';
return JSON.stringify(data);
}
return data;
}
];
// 实例请求转换器
const api = axios.create({
transformRequest: [
function (data, headers) {
// 自定义转换逻辑
if (data && typeof data === 'object' && !(data instanceof FormData)) {
return JSON.stringify({
...data,
timestamp: Date.now()
});
}
return data;
}
]
});
9.2 响应转换器
// 全局响应转换器
axios.defaults.transformResponse = [
function (data) {
// 尝试解析 JSON
if (typeof data === 'string') {
try {
data = JSON.parse(data);
} catch (e) {
/* 忽略解析错误 */
}
}
// 统一处理数据格式
if (data && typeof data === 'object' && data.data) {
return data.data;
}
return data;
}
];
// 实例响应转换器
const api = axios.create({
transformResponse: [
function (data) {
// 添加时间戳
return {
data: data,
receivedAt: new Date().toISOString()
};
}
]
});
10. 文件上传和下载
10.1 文件上传
// 上传单个文件
function uploadFile(file) {
const formData = new FormData();
formData.append('file', file);
formData.append('description', '文件描述');
return axios.post('/api/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data'
},
onUploadProgress: function (progressEvent) {
// 处理上传进度
if (progressEvent.lengthComputable) {
const percentCompleted = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
console.log('上传进度:', percentCompleted + '%');
}
}
});
}
// 上传多个文件
function uploadMultipleFiles(files) {
const formData = new FormData();
Array.from(files).forEach((file, index) => {
formData.append(`files[${index}]`, file);
});
return axios.post('/api/upload-multiple', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
});
}
10.2 文件下载
// 下载文件
function downloadFile(fileId) {
return axios({
method: 'get',
url: `/api/download/${fileId}`,
responseType: 'blob', // 重要:设置响应类型为 blob
onDownloadProgress: function (progressEvent) {
// 处理下载进度
if (progressEvent.lengthComputable) {
const percentCompleted = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
console.log('下载进度:', percentCompleted + '%');
}
}
}).then(response => {
// 创建下载链接
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', 'filename.ext');
document.body.appendChild(link);
link.click();
// 清理
link.remove();
window.URL.revokeObjectURL(url);
});
}
// 下载并获取文件信息
async function downloadWithInfo(fileId) {
try {
const response = await axios({
method: 'get',
url: `/api/download/${fileId}`,
responseType: 'blob'
});
// 从响应头获取文件名
const contentDisposition = response.headers['content-disposition'];
let filename = 'downloaded-file';
if (contentDisposition) {
const filenameMatch = contentDisposition.match(/filename="?(.+)"?/);
if (filenameMatch && filenameMatch[1]) {
filename = filenameMatch[1];
}
}
// 下载文件
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', filename);
document.body.appendChild(link);
link.click();
link.remove();
window.URL.revokeObjectURL(url);
return { success: true, filename };
} catch (error) {
console.error('下载失败:', error);
return { success: false, error: error.message };
}
}
11. 并发请求
11.1 Promise.all
// 并发执行多个请求
function fetchUserData(userId) {
return axios.get(`/api/users/${userId}`);
}
function fetchUserPosts(userId) {
return axios.get(`/api/users/${userId}/posts`);
}
function fetchUserComments(userId) {
return axios.get(`/api/users/${userId}/comments`);
}
// 使用 Promise.all
async function fetchUserDetails(userId) {
try {
const [userResponse, postsResponse, commentsResponse] = await Promise.all([
fetchUserData(userId),
fetchUserPosts(userId),
fetchUserComments(userId)
]);
return {
user: userResponse.data,
posts: postsResponse.data,
comments: commentsResponse.data
};
} catch (error) {
console.error('获取用户详情失败:', error);
throw error;
}
}
11.2 axios.all 和 axios.spread
// 使用 axios.all 和 axios.spread
function fetchAllData() {
return axios.all([
axios.get('/api/users'),
axios.get('/api/posts'),
axios.get('/api/comments')
]).then(axios.spread((users, posts, comments) => {
return {
users: users.data,
posts: posts.data,
comments: comments.data
};
}));
}
12. 实际应用示例
12.1 API 客户端封装
// API 客户端封装
class ApiClient {
constructor(baseURL, options = {}) {
this.client = axios.create({
baseURL,
timeout: options.timeout || 10000,
headers: {
'Content-Type': 'application/json',
...options.headers
}
});
this.setupInterceptors();
}
setupInterceptors() {
// 请求拦截器
this.client.interceptors.request.use(
config => {
// 添加认证 token
const token = this.getToken();
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
// 添加请求 ID 用于追踪
config.headers['X-Request-ID'] = this.generateRequestId();
return config;
},
error => Promise.reject(error)
);
// 响应拦截器
this.client.interceptors.response.use(
response => {
// 统一处理响应数据
return response.data || response;
},
error => {
// 统一错误处理
this.handleError(error);
return Promise.reject(error);
}
);
}
getToken() {
return localStorage.getItem('authToken');
}
generateRequestId() {
return 'req_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
}
handleError(error) {
if (error.response) {
const { status, data } = error.response;
switch (status) {
case 401:
this.handleUnauthorized();
break;
case 403:
console.error('权限不足:', data.message);
break;
case 404:
console.error('资源不存在:', data.message);
break;
case 422:
console.error('数据验证失败:', data.errors);
break;
case 500:
console.error('服务器内部错误');
break;
default:
console.error('请求失败:', data.message || '未知错误');
}
} else if (error.request) {
console.error('网络错误,请检查网络连接');
} else {
console.error('请求配置错误:', error.message);
}
}
handleUnauthorized() {
// 清除本地存储的 token
localStorage.removeItem('authToken');
// 重定向到登录页
window.location.href = '/login';
}
// 基础方法
get(url, config = {}) {
return this.client.get(url, config);
}
post(url, data = {}, config = {}) {
return this.client.post(url, data, config);
}
put(url, data = {}, config = {}) {
return this.client.put(url, data, config);
}
delete(url, config = {}) {
return this.client.delete(url, config);
}
patch(url, data = {}, config = {}) {
return this.client.patch(url, data, config);
}
}
// 使用示例
const api = new ApiClient('https://api.example.com');
// API 调用
api.get('/users')
.then(users => console.log(users))
.catch(error => console.error(error));
api.post('/users', { name: 'John', email: 'john@example.com' })
.then(user => console.log(user))
.catch(error => console.error(error));
12.2 带缓存的 API 客户端
// 带缓存的 API 客户端
class CachedApiClient extends ApiClient {
constructor(baseURL, options = {}) {
super(baseURL, options);
this.cache = new Map();
this.cacheTimeout = options.cacheTimeout || 300000; // 5分钟
}
async get(url, config = {}) {
// 检查是否启用缓存
if (config.cache !== false) {
const cacheKey = this.generateCacheKey(url, config);
const cached = this.cache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < this.cacheTimeout) {
console.log('从缓存获取数据:', url);
return cached.data;
}
}
try {
const response = await this.client.get(url, config);
// 缓存响应数据
if (config.cache !== false) {
const cacheKey = this.generateCacheKey(url, config);
this.cache.set(cacheKey, {
data: response,
timestamp: Date.now()
});
}
return response;
} catch (error) {
// 如果请求失败但有缓存,返回缓存数据
if (config.cacheFallback && config.cache !== false) {
const cacheKey = this.generateCacheKey(url, config);
const cached = this.cache.get(cacheKey);
if (cached) {
console.warn('请求失败,使用缓存数据:', url);
return cached.data;
}
}
throw error;
}
}
generateCacheKey(url, config) {
return url + JSON.stringify(config.params || {});
}
clearCache() {
this.cache.clear();
}
removeCache(url) {
for (const key of this.cache.keys()) {
if (key.startsWith(url)) {
this.cache.delete(key);
}
}
}
}
// 使用带缓存的客户端
const cachedApi = new CachedApiClient('https://api.example.com');
// 启用缓存的请求
cachedApi.get('/users', { cache: true });
// 禁用缓存的请求
cachedApi.get('/users', { cache: false });
// 启用缓存且失败时使用缓存的请求
cachedApi.get('/users', { cache: true, cacheFallback: true });
12.3 上传进度组件
// 上传进度组件
class UploadProgress {
constructor(containerId) {
this.container = document.getElementById(containerId);
this.setupUI();
}
setupUI() {
this.container.innerHTML = `
<div class="upload-area">
<input type="file" id="fileInput" multiple>
<div id="progressContainer" style="display: none;">
<div class="progress-bar">
<div class="progress-fill"></div>
</div>
<div class="progress-text">0%</div>
</div>
<div id="result"></div>
</div>
`;
this.fileInput = this.container.querySelector('#fileInput');
this.progressContainer = this.container.querySelector('#progressContainer');
this.progressFill = this.container.querySelector('.progress-fill');
this.progressText = this.container.querySelector('.progress-text');
this.result = this.container.querySelector('#result');
this.fileInput.addEventListener('change', (e) => {
this.handleFileSelect(e.target.files);
});
}
async handleFileSelect(files) {
if (files.length === 0) return;
for (let i = 0; i < files.length; i++) {
await this.uploadFile(files[i]);
}
}
async uploadFile(file) {
const formData = new FormData();
formData.append('file', file);
try {
this.showProgress();
const response = await axios.post('/api/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data'
},
onUploadProgress: (progressEvent) => {
if (progressEvent.lengthComputable) {
const percentCompleted = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
this.updateProgress(percentCompleted);
}
}
});
this.uploadComplete(response.data);
} catch (error) {
this.uploadError(error);
}
}
showProgress() {
this.progressContainer.style.display = 'block';
this.updateProgress(0);
}
updateProgress(percent) {
this.progressFill.style.width = percent + '%';
this.progressText.textContent = percent + '%';
}
uploadComplete(data) {
this.updateProgress(100);
this.result.innerHTML = `<div class="success">上传成功: ${data.filename}</div>`;
setTimeout(() => {
this.progressContainer.style.display = 'none';
this.result.innerHTML = '';
}, 2000);
}
uploadError(error) {
this.progressContainer.style.display = 'none';
this.result.innerHTML = `<div class="error">上传失败: ${error.message}</div>`;
setTimeout(() => {
this.result.innerHTML = '';
}, 3000);
}
}
// 使用上传组件
const uploader = new UploadProgress('uploadContainer');
Axios 是一个功能强大且易用的 HTTP 客户端,通过合理配置和使用,可以大大简化前端的网络请求处理。在实际项目中,建议根据具体需求进行适当的封装和定制。
790

被折叠的 条评论
为什么被折叠?



