Axios 完整实战教程

Axios 完整实战教程

本教程使用最新JavaScript语法,每个案例独立HTML文件,配合Mermaid图表详细讲解


目录

第一部分:基础概念

第二部分:后端开发

第三部分:前端请求(Axios)


第一章:HTTP协议详解

1.1 HTTP协议概述

HTTP(HyperText Transfer Protocol)是超文本传输协议,是Web浏览器与服务器之间通信的基础协议。

HTTP工作原理

服务器:3000 浏览器 服务器:3000 浏览器 GET /users HTTP/1.1 Host: localhost {name: "李昊哲"} HTTP/1.1 200 OK Content-Type: json {success: true} HTTP请求 HTTP响应

HTTP请求与响应结构

📥 HTTP响应
HTTP/1.1 200 OK
Content-Type: application/json
{success: true}
📤 HTTP请求
GET /users HTTP/1.1
Host: localhost
Content-Type: application/json
{name: '李昊哲'}

1.2 HTTP请求方法

方法对比表

方法 用途 请求体 响应状态码 幂等性
GET 查询数据 无 200 是
POST 新增数据 有 201 否
PUT 完整更新 有 200 是
PATCH 部分更新 有 200 否
DELETE 删除数据 无 204 是

RESTful流程图

数据库 Express API 浏览器 数据库 Express API 浏览器 GET /users 获取用户列表 SELECT * FROM users 用户1, 用户2, ... 200 OK + JSON数据 POST /users 创建用户 INSERT INTO users VALUES(...) 新用户ID 201 Created + 新用户数据 GET /users/1 获取ID=1用户 SELECT * FROM users WHERE id=1 {id:1, name:"张三"} 200 OK + 用户数据 PUT /users/1 更新用户 UPDATE users SET ... 更新后的用户 200 OK + 更新后数据 DELETE /users/1 删除用户 DELETE FROM users WHERE id=1 影响行数: 1 204 No Content

1.3 HTTP状态码详解

常见状态码

状态码 含义 常见场景
200 成功 GET/PUT/PATCH成功
201 已创建 POST创建资源成功
204 无内容 DELETE成功,无返回体
400 请求错误 参数校验失败
401 未授权 未登录或Token过期
404 资源不存在 请求的ID不存在
500 服务器错误 后端代码异常

第二章:Express后端开发

2.1 环境搭建

安装命令

bash 复制代码
mkdir axios-express-tutorial
cd axios-express-tutorial
npm init -y
npm install express@5.2.1 cors body-parser dotenv
npm install -D nodemon

2.2 完整server.js代码

javascript 复制代码
// server.js
import express from 'express';
import cors from 'cors';
import { Router } from 'express';
import multer from 'multer';
import path from 'path';
import fs from 'fs';
import { fileURLToPath } from 'url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const app = express();
const PORT = 3000;

app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(express.static(path.join(__dirname, 'public')));

const router = Router();

const users = [
    { id: 1, name: '张三', email: 'zhangsan@example.com', age: 25 },
    { id: 2, name: '李四', email: 'lisi@example.com', age: 30 },
    { id: 3, name: '王五', email: 'wangwu@example.com', age: 28 }
];

const storage = multer.diskStorage({
    destination: (req, file, cb) => {
        const uploadDir = 'uploads/';
        if (!fs.existsSync(uploadDir)) {
            fs.mkdirSync(uploadDir, { recursive: true });
        }
        cb(null, uploadDir);
    },
    filename: (req, file, cb) => {
        const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
        const ext = path.extname(file.originalname);
        cb(null, `file-${uniqueSuffix}${ext}`);
    }
});

const upload = multer({ storage, limits: { fileSize: 10 * 1024 * 1024 } });

router.get('/users', (req, res) => {
    res.json({ success: true, data: users });
});

router.get('/users/:id', (req, res) => {
    const id = parseInt(req.params.id);
    const user = users.find(u => u.id === id);
    if (!user) return res.status(404).json({ success: false, error: '用户不存在' });
    res.json({ success: true, data: user });
});

router.post('/users', (req, res) => {
    const { name, email, age } = req.body;
    if (!name || !email) {
        return res.status(400).json({ success: false, error: '姓名和邮箱不能为空' });
    }
    const newUser = {
        id: users.length > 0 ? Math.max(...users.map(u => u.id)) + 1 : 1,
        name, email, age: parseInt(age) || 0
    };
    users.push(newUser);
    res.status(201).json({ success: true, data: newUser });
});

router.put('/users/:id', (req, res) => {
    const id = parseInt(req.params.id);
    const user = users.find(u => u.id === id);
    if (!user) return res.status(404).json({ success: false, error: '用户不存在' });
    const { name, email, age } = req.body;
    user.name = name || user.name;
    user.email = email || user.email;
    user.age = parseInt(age) || user.age;
    res.json({ success: true, data: user });
});

router.patch('/users/:id', (req, res) => {
    const id = parseInt(req.params.id);
    const user = users.find(u => u.id === id);
    if (!user) return res.status(404).json({ success: false, error: '用户不存在' });
    Object.assign(user, req.body);
    res.json({ success: true, data: user });
});

router.delete('/users/:id', (req, res) => {
    const id = parseInt(req.params.id);
    const index = users.findIndex(u => u.id === id);
    if (index === -1) return res.status(404).json({ success: false, error: '用户不存在' });
    users.splice(index, 1);
    res.status(204).json({ success: true });
});

router.post('/login', (req, res) => {
    const { username, password } = req.body;
    if (username === 'admin' && password === '123456') {
        const token = 'jwt-token-' + Date.now();
        res.json({ success: true, data: { token, user: { id: 1, username: 'admin', nickname: '管理员' } } });
    } else {
        res.status(401).json({ success: false, error: '账号或密码错误' });
    }
});

router.get('/profile', (req, res) => {
    const authHeader = req.headers.authorization;
    if (!authHeader || !authHeader.startsWith('Bearer ')) {
        return res.status(401).json({ success: false, error: '未授权' });
    }
    res.json({ success: true, data: { id: 1, username: 'admin', nickname: '管理员' } });
});

router.post('/upload', upload.single('file'), (req, res) => {
    if (!req.file) return res.status(400).json({ success: false, error: '没有上传文件' });
    res.json({
        success: true,
        data: {
            filename: req.file.filename,
            originalName: req.file.originalname,
            size: req.file.size,
            path: `/uploads/${req.file.filename}`
        }
    });
});

app.use('/api', router);

app.listen(PORT, () => {
    console.log(`服务器运行在 http://localhost:${PORT}`);
});

第三章:Axios基础用法

3.1 安装与引入

CDN引入(浏览器环境)

html 复制代码
<script src="https://cdn.jsdelivr.net/npm/axios@1.15.0/dist/axios.min.js"></script>

npm安装(Node.js/模块化环境)

bash 复制代码
npm install axios
javascript 复制代码
// ES6模块化导入
import axios from 'axios';

// 或按需导入
import axios, { isAxiosError, AxiosError } from 'axios';

3.2 基础请求方法

GET请求 - 获取数据

javascript 复制代码
// 方式1:简洁写法
const res = await axios.get('http://localhost:3000/api/users');

// 方式2:带查询参数
const res = await axios.get('http://localhost:3000/api/users', {
    params: {
        page: 1,
        limit: 10,
        keyword: '张三'
    }
});

// 方式3:完整配置
const res = await axios({
    method: 'get',
    url: 'http://localhost:3000/api/users',
    params: { page: 1 }
});

POST请求 - 创建数据

javascript 复制代码
// 方式1:简洁写法
const res = await axios.post('http://localhost:3000/api/users', {
    name: '张三',
    email: 'zhangsan@example.com'
});

// 方式2:带配置选项
const res = await axios.post('http://localhost:3000/api/users', userData, {
    headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer token'
    },
    timeout: 5000
});

PUT/PATCH请求 - 更新数据

javascript 复制代码
// PUT - 完整更新
const res = await axios.put(`http://localhost:3000/api/users/${id}`, {
    name: '张三',
    email: 'zhangsan@example.com',
    age: 25
});

// PATCH - 部分更新
const res = await axios.patch(`http://localhost:3000/api/users/${id}`, {
    age: 26  // 只更新年龄
});

DELETE请求 - 删除数据

javascript 复制代码
// 方式1:简洁写法
await axios.delete(`http://localhost:3000/api/users/${id}`);

// 方式2:带请求体(部分服务器支持)
await axios.delete('http://localhost:3000/api/users', {
    data: { ids: [1, 2, 3] }  // 批量删除
});

3.3 请求配置选项

常用配置项

javascript 复制代码
const res = await axios({
    // URL相关
    url: '/users',
    method: 'get',           // 默认: get
    baseURL: 'http://localhost:3000/api',
    
    // 数据相关
    params: { page: 1 },     // URL查询参数
    data: { name: '张三' },   // 请求体数据
    
    // 配置相关
    headers: { 'X-Requested-With': 'XMLHttpRequest' },
    timeout: 10000,          // 超时时间(毫秒)
    
    // 响应处理
    responseType: 'json',    // 响应数据类型: json/arraybuffer/blob/text/stream
    
    // 跨域配置
    withCredentials: false   // 是否携带cookie
});

响应对象结构

javascript 复制代码
const res = await axios.get('/api/users');

console.log(res.data);       // 服务器返回的数据
console.log(res.status);     // HTTP状态码 (200, 404, etc.)
console.log(res.statusText); // 状态文本 ('OK', 'Not Found')
console.log(res.headers);    // 响应头
console.log(res.config);     // 请求配置
console.log(res.request);    // 原生请求对象

错误处理基础

javascript 复制代码
try {
    const res = await axios.get('/api/users');
} catch (error) {
    if (error.response) {
        // 服务器响应了错误状态码 (4xx, 5xx)
        console.log('状态码:', error.response.status);
        console.log('错误数据:', error.response.data);
    } else if (error.request) {
        // 请求已发送但没有收到响应
        console.log('无响应:', error.request);
    } else {
        // 请求配置出错
        console.log('错误:', error.message);
    }
}

第四章:Axios实战案例

4.1 案例一:用户列表查询(GET)

流程图

是
否
打开HTML页面
页面加载完成后
调用axios.get发送GET请求
请求成功?
渲染用户列表到表格
显示错误提示

前端HTML

html 复制代码
<!-- public/01-get-users.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>用户列表查询 - GET请求</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { font-family: 'Segoe UI', sans-serif; background: #f5f7fa; padding: 20px; }
        .container { max-width: 900px; margin: 0 auto; }
        h1 { color: #1a1a2e; margin-bottom: 20px; text-align: center; }
        .card { background: white; border-radius: 12px; padding: 24px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); margin-bottom: 20px; }
        .method-badge { display: inline-block; background: #10b981; color: white; padding: 4px 12px; border-radius: 4px; font-size: 12px; font-weight: bold; }
        table { width: 100%; border-collapse: collapse; margin-top: 16px; }
        th, td { padding: 12px; text-align: left; border-bottom: 1px solid #e5e7eb; }
        th { background: #f8fafc; font-weight: 600; color: #374151; }
        tr:hover { background: #f9fafb; }
        .btn { padding: 6px 14px; border: none; border-radius: 6px; cursor: pointer; font-size: 14px; transition: all 0.2s; }
        .btn-delete { background: #ef4444; color: white; }
        .btn-delete:hover { background: #dc2626; }
        .loading { text-align: center; padding: 40px; color: #6b7280; }
        .error { background: #fef2f2; color: #dc2626; padding: 12px; border-radius: 8px; margin-top: 16px; }
    </style>
</head>
<body>
    <div class="container">
        <h1>用户列表查询 <span class="method-badge">GET</span></h1>
        <div class="card">
            <h2 style="margin-bottom:12px;">📋 用户列表</h2>
            <div id="loading" class="loading">加载中...</div>
            <div id="error" class="error" style="display:none;"></div>
            <table id="userTable" style="display:none;">
                <thead>
                    <tr><th>ID</th><th>姓名</th><th>邮箱</th><th>年龄</th><th>操作</th></tr>
                </thead>
                <tbody id="userList"></tbody>
            </table>
        </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/axios@1.15.0/dist/axios.min.js"></script>
    <script>
        const API_BASE = 'http://localhost:3000/api';

        async function loadUsers() {
            const loadingEl = document.getElementById('loading');
            const errorEl = document.getElementById('error');
            const tableEl = document.getElementById('userTable');
            const listEl = document.getElementById('userList');

            try {
                const res = await axios.get(`${API_BASE}/users`);
                loadingEl.style.display = 'none';
                tableEl.style.display = 'table';
                listEl.innerHTML = res.data.map(user => `
                    <tr>
                        <td>${user.id}</td>
                        <td>${user.name}</td>
                        <td>${user.email}</td>
                        <td>${user.age}</td>
                        <td><button class="btn btn-delete" onclick="deleteUser(${user.id})">删除</button></td>
                    </tr>
                `).join('');
            } catch (err) {
                loadingEl.style.display = 'none';
                errorEl.style.display = 'block';
                errorEl.textContent = `加载失败: ${err.response?.data?.error || err.message}`;
            }
        }

        async function deleteUser(id) {
            if (!confirm('确定要删除此用户吗?')) return;
            try {
                await axios.delete(`${API_BASE}/users/${id}`);
                loadUsers();
            } catch (err) {
                alert(`删除失败: ${err.response?.data?.error || err.message}`);
            }
        }

        loadUsers();
    </script>
</body>
</html>

4.2 案例二:用户创建(POST)

流程图

否
是
是
否
用户填写表单
点击提交按钮
前端校验表单
校验通过?
显示校验错误
调用axios.post
请求成功?
显示成功提示
显示错误提示

前端HTML

html 复制代码
<!-- public/02-create-user.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>用户创建 - POST请求</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { font-family: 'Segoe UI', sans-serif; background: #f5f7fa; padding: 20px; }
        .container { max-width: 500px; margin: 0 auto; }
        h1 { color: #1a1a2e; margin-bottom: 20px; text-align: center; }
        .card { background: white; border-radius: 12px; padding: 24px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
        .method-badge { display: inline-block; background: #f59e0b; color: white; padding: 4px 12px; border-radius: 4px; font-size: 12px; font-weight: bold; }
        .form-group { margin-bottom: 16px; }
        label { display: block; margin-bottom: 6px; font-weight: 500; color: #374151; }
        input { width: 100%; padding: 10px 12px; border: 1px solid #d1d5db; border-radius: 6px; font-size: 14px; transition: border-color 0.2s; }
        input:focus { outline: none; border-color: #3b82f6; }
        .btn { width: 100%; padding: 12px; background: #3b82f6; color: white; border: none; border-radius: 6px; font-size: 16px; cursor: pointer; transition: background 0.2s; }
        .btn:hover { background: #2563eb; }
        .message { padding: 12px; border-radius: 8px; margin-top: 16px; text-align: center; }
        .success { background: #d1fae5; color: #065f46; }
        .error { background: #fee2e2; color: #991b1b; }
    </style>
</head>
<body>
    <div class="container">
        <h1>用户创建 <span class="method-badge">POST</span></h1>
        <div class="card">
            <form id="userForm">
                <div class="form-group">
                    <label>姓名</label>
                    <input type="text" name="name" placeholder="请输入姓名" required>
                </div>
                <div class="form-group">
                    <label>邮箱</label>
                    <input type="email" name="email" placeholder="请输入邮箱" required>
                </div>
                <div class="form-group">
                    <label>年龄</label>
                    <input type="number" name="age" placeholder="请输入年龄" min="1" max="150">
                </div>
                <button type="submit" class="btn">创建用户</button>
            </form>
            <div id="message" class="message" style="display:none;"></div>
        </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/axios@1.15.0/dist/axios.min.js"></script>
    <script>
        const API_BASE = 'http://localhost:3000/api';
        const form = document.getElementById('userForm');
        const messageEl = document.getElementById('message');

        form.addEventListener('submit', async (e) => {
            e.preventDefault();
            const formData = new FormData(form);
            const data = Object.fromEntries(formData.entries());
            showMessage('创建中...', 'info');
            try {
                const res = await axios.post(`${API_BASE}/users`, data);
                showMessage(`用户 "${res.data.data.name}" 创建成功!`, 'success');
                form.reset();
            } catch (err) {
                showMessage(`创建失败: ${err.response?.data?.error || err.message}`, 'error');
            }
        });

        function showMessage(text, type) {
            messageEl.textContent = text;
            messageEl.className = `message ${type}`;
            messageEl.style.display = 'block';
        }
    </script>
</body>
</html>

4.3 案例三:用户更新(PUT/PATCH)

流程图

是
否
点击编辑按钮
弹出编辑对话框
显示当前用户数据
用户修改数据
点击保存按钮
调用axios.patch
请求成功?
关闭对话框并刷新列表
显示错误提示

前端HTML

html 复制代码
<!-- public/03-update-user.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>用户更新 - PUT/PATCH请求</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { font-family: 'Segoe UI', sans-serif; background: #f5f7fa; padding: 20px; }
        .container { max-width: 900px; margin: 0 auto; }
        h1 { color: #1a1a2e; margin-bottom: 20px; text-align: center; }
        .card { background: white; border-radius: 12px; padding: 24px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); margin-bottom: 20px; }
        .method-badges { display: flex; gap: 8px; margin-bottom: 16px; }
        .method-badge { padding: 4px 12px; border-radius: 4px; font-size: 12px; font-weight: bold; color: white; }
        .badge-put { background: #8b5cf6; }
        .badge-patch { background: #06b6d4; }
        table { width: 100%; border-collapse: collapse; }
        th, td { padding: 12px; text-align: left; border-bottom: 1px solid #e5e7eb; }
        th { background: #f8fafc; font-weight: 600; color: #374151; }
        tr:hover { background: #f9fafb; }
        .btn { padding: 6px 14px; border: none; border-radius: 6px; cursor: pointer; font-size: 14px; transition: all 0.2s; }
        .btn-edit { background: #3b82f6; color: white; }
        .btn-edit:hover { background: #2563eb; }
        .modal { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); justify-content: center; align-items: center; }
        .modal.active { display: flex; }
        .modal-content { background: white; border-radius: 12px; padding: 24px; width: 400px; max-width: 90%; }
        .modal h2 { margin-bottom: 16px; }
        .form-group { margin-bottom: 12px; }
        label { display: block; margin-bottom: 4px; font-weight: 500; }
        input { width: 100%; padding: 8px 12px; border: 1px solid #d1d5db; border-radius: 6px; }
        .modal-btns { display: flex; gap: 8px; margin-top: 16px; }
        .btn-cancel { flex: 1; padding: 10px; background: #9ca3af; color: white; border: none; border-radius: 6px; cursor: pointer; }
        .btn-save { flex: 1; padding: 10px; background: #10b981; color: white; border: none; border-radius: 6px; cursor: pointer; }
        .error { background: #fef2f2; color: #dc2626; padding: 12px; border-radius: 8px; margin-bottom: 16px; }
        .success { background: #d1fae5; color: #065f46; padding: 12px; border-radius: 8px; margin-bottom: 16px; }
    </style>
</head>
<body>
    <div class="container">
        <h1>用户更新 <span class="method-badges"><span class="method-badge badge-put">PUT</span><span class="method-badge badge-patch">PATCH</span></span></h1>
        <div class="card">
            <div id="error" class="error" style="display:none;"></div>
            <div id="success" class="success" style="display:none;"></div>
            <table>
                <thead>
                    <tr><th>ID</th><th>姓名</th><th>邮箱</th><th>年龄</th><th>操作</th></tr>
                </thead>
                <tbody id="userList"></tbody>
            </table>
        </div>
    </div>
    <div id="editModal" class="modal">
        <div class="modal-content">
            <h2>编辑用户</h2>
            <form id="editForm">
                <input type="hidden" name="id">
                <div class="form-group">
                    <label>姓名</label>
                    <input type="text" name="name" required>
                </div>
                <div class="form-group">
                    <label>邮箱</label>
                    <input type="email" name="email" required>
                </div>
                <div class="form-group">
                    <label>年龄</label>
                    <input type="number" name="age" min="1">
                </div>
                <div class="modal-btns">
                    <button type="button" class="btn-cancel" onclick="closeModal()">取消</button>
                    <button type="submit" class="btn-save">保存</button>
                </div>
            </form>
        </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/axios@1.15.0/dist/axios.min.js"></script>
    <script>
        const API_BASE = 'http://localhost:3000/api';
        let editingUser = null;

        async function loadUsers() {
            try {
                const res = await axios.get(`${API_BASE}/users`);
                const tbody = document.getElementById('userList');
                tbody.innerHTML = res.data.map(user => `
                    <tr>
                        <td>${user.id}</td>
                        <td>${user.name}</td>
                        <td>${user.email}</td>
                        <td>${user.age}</td>
                        <td><button class="btn btn-edit" onclick="openEditModal(${user.id})">编辑</button></td>
                    </tr>
                `).join('');
            } catch (err) {
                showError(`加载失败: ${err.message}`);
            }
        }

        async function openEditModal(id) {
            try {
                const res = await axios.get(`${API_BASE}/users/${id}`);
                editingUser = res.data.data;
                document.querySelector('#editForm input[name="id"]').value = editingUser.id;
                document.querySelector('#editForm input[name="name"]').value = editingUser.name;
                document.querySelector('#editForm input[name="email"]').value = editingUser.email;
                document.querySelector('#editForm input[name="age"]').value = editingUser.age;
                document.getElementById('editModal').classList.add('active');
            } catch (err) {
                showError(`获取用户失败: ${err.message}`);
            }
        }

        function closeModal() {
            document.getElementById('editModal').classList.remove('active');
            editingUser = null;
        }

        document.getElementById('editForm').addEventListener('submit', async (e) => {
            e.preventDefault();
            const formData = new FormData(e.target);
            const data = Object.fromEntries(formData.entries());
            try {
                await axios.patch(`${API_BASE}/users/${data.id}`, data);
                closeModal();
                loadUsers();
                showSuccess('用户更新成功!');
            } catch (err) {
                showError(`更新失败: ${err.response?.data?.error || err.message}`);
            }
        });

        function showError(msg) {
            const el = document.getElementById('error');
            el.textContent = msg;
            el.style.display = 'block';
            setTimeout(() => el.style.display = 'none', 3000);
        }

        function showSuccess(msg) {
            const el = document.getElementById('success');
            el.textContent = msg;
            el.style.display = 'block';
            setTimeout(() => el.style.display = 'none', 3000);
        }

        loadUsers();
    </script>
</body>
</html>

4.4 案例四:用户删除(DELETE)

流程图

取消
确认
是
否
点击删除按钮
确认删除?
不做任何操作
调用axios.delete
请求成功?
显示删除成功提示并刷新列表
显示错误提示

前端HTML

html 复制代码
<!-- public/04-delete-user.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>用户删除 - DELETE请求</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { font-family: 'Segoe UI', sans-serif; background: #f5f7fa; padding: 20px; }
        .container { max-width: 900px; margin: 0 auto; }
        h1 { color: #1a1a2e; margin-bottom: 20px; text-align: center; }
        .card { background: white; border-radius: 12px; padding: 24px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); margin-bottom: 20px; }
        .method-badge { display: inline-block; background: #ef4444; color: white; padding: 4px 12px; border-radius: 4px; font-size: 12px; font-weight: bold; }
        table { width: 100%; border-collapse: collapse; }
        th, td { padding: 12px; text-align: left; border-bottom: 1px solid #e5e7eb; }
        th { background: #f8fafc; font-weight: 600; color: #374151; }
        tr:hover { background: #f9fafb; }
        .btn { padding: 6px 14px; border: none; border-radius: 6px; cursor: pointer; font-size: 14px; transition: all 0.2s; }
        .btn-delete { background: #ef4444; color: white; }
        .btn-delete:hover { background: #dc2626; }
        .loading { text-align: center; padding: 40px; color: #6b7280; }
        .toast { position: fixed; top: 20px; right: 20px; padding: 12px 20px; border-radius: 8px; color: white; font-weight: 500; opacity: 0; transform: translateX(100%); transition: all 0.3s; }
        .toast.show { opacity: 1; transform: translateX(0); }
        .toast.success { background: #10b981; }
        .toast.error { background: #ef4444; }
    </style>
</head>
<body>
    <div class="container">
        <h1>用户删除 <span class="method-badge">DELETE</span></h1>
        <div class="card">
            <div id="loading" class="loading">加载中...</div>
            <table id="userTable" style="display:none;">
                <thead>
                    <tr><th>ID</th><th>姓名</th><th>邮箱</th><th>年龄</th><th>操作</th></tr>
                </thead>
                <tbody id="userList"></tbody>
            </table>
        </div>
    </div>
    <div id="toast" class="toast"></div>
    <script src="https://cdn.jsdelivr.net/npm/axios@1.15.0/dist/axios.min.js"></script>
    <script>
        const API_BASE = 'http://localhost:3000/api';

        async function loadUsers() {
            const loadingEl = document.getElementById('loading');
            const tableEl = document.getElementById('userTable');
            try {
                const res = await axios.get(`${API_BASE}/users`);
                loadingEl.style.display = 'none';
                tableEl.style.display = 'table';
                const tbody = document.getElementById('userList');
                tbody.innerHTML = res.data.map(user => `
                    <tr>
                        <td>${user.id}</td>
                        <td>${user.name}</td>
                        <td>${user.email}</td>
                        <td>${user.age}</td>
                        <td><button class="btn btn-delete" onclick="deleteUser(${user.id}, '${user.name}')">删除</button></td>
                    </tr>
                `).join('');
            } catch (err) {
                loadingEl.textContent = `加载失败: ${err.message}`;
            }
        }

        async function deleteUser(id, name) {
            if (!confirm(`确定要删除用户 "${name}" 吗?此操作不可撤销。`)) return;
            try {
                await axios.delete(`${API_BASE}/users/${id}`);
                showToast('删除成功!', 'success');
                loadUsers();
            } catch (err) {
                showToast(`删除失败: ${err.response?.data?.error || err.message}`, 'error');
            }
        }

        function showToast(message, type) {
            const toast = document.getElementById('toast');
            toast.textContent = message;
            toast.className = `toast ${type} show`;
            setTimeout(() => toast.classList.remove('show'), 3000);
        }

        loadUsers();
    </script>
</body>
</html>

4.5 案例五:用户登录(POST + JWT)

流程图

是
否
用户输入账号密码
点击登录按钮
调用axios.post发送登录请求
请求成功?
保存Token到localStorage
显示错误提示
跳转到用户列表页面

前端HTML

html 复制代码
<!-- public/05-login.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>用户登录 - POST请求</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { font-family: 'Segoe UI', sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; display: flex; justify-content: center; align-items: center; }
        .login-card { background: white; border-radius: 16px; padding: 40px; width: 400px; max-width: 90%; box-shadow: 0 20px 60px rgba(0,0,0,0.3); }
        h1 { text-align: center; color: #1a1a2e; margin-bottom: 8px; }
        .subtitle { text-align: center; color: #6b7280; margin-bottom: 30px; }
        .form-group { margin-bottom: 20px; }
        label { display: block; margin-bottom: 8px; font-weight: 500; color: #374151; }
        input { width: 100%; padding: 12px 16px; border: 2px solid #e5e7eb; border-radius: 8px; font-size: 16px; transition: border-color 0.2s; }
        input:focus { outline: none; border-color: #667eea; }
        .btn { width: 100%; padding: 14px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; border-radius: 8px; font-size: 16px; font-weight: 600; cursor: pointer; transition: transform 0.2s; }
        .btn:hover { transform: translateY(-2px); }
        .btn:disabled { opacity: 0.7; cursor: not-allowed; }
        .message { padding: 12px; border-radius: 8px; margin-top: 16px; text-align: center; }
        .error { background: #fee2e2; color: #991b1b; }
        .success { background: #d1fae5; color: #065f46; }
        .demo-hint { margin-top: 20px; padding: 12px; background: #f3f4f6; border-radius: 8px; font-size: 13px; color: #6b7280; }
        .demo-hint strong { color: #374151; }
    </style>
</head>
<body>
    <div class="login-card">
        <h1>用户登录</h1>
        <p class="subtitle">请输入账号和密码</p>
        <form id="loginForm">
            <div class="form-group">
                <label>账号</label>
                <input type="text" name="username" placeholder="请输入账号" required value="admin">
            </div>
            <div class="form-group">
                <label>密码</label>
                <input type="password" name="password" placeholder="请输入密码" required value="123456">
            </div>
            <button type="submit" class="btn">登 录</button>
        </form>
        <div id="message" class="message" style="display:none;"></div>
        <div class="demo-hint">
            <strong>测试账号:</strong>admin / 123456
        </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/axios@1.15.0/dist/axios.min.js"></script>
    <script>
        const API_BASE = 'http://localhost:3000/api';
        const form = document.getElementById('loginForm');
        const messageEl = document.getElementById('message');

        form.addEventListener('submit', async (e) => {
            e.preventDefault();
            const formData = new FormData(form);
            const data = Object.fromEntries(formData.entries());
            const btn = form.querySelector('.btn');
            btn.disabled = true;
            btn.textContent = '登录中...';
            hideMessage();
            try {
                const res = await axios.post(`${API_BASE}/login`, data);
                if (res.data.success) {
                    localStorage.setItem('token', res.data.data.token);
                    localStorage.setItem('user', JSON.stringify(res.data.data.user));
                    showMessage('登录成功!正在跳转...', 'success');
                    setTimeout(() => { window.location.href = '01-get-users.html'; }, 1000);
                }
            } catch (err) {
                showMessage(err.response?.data?.error || '登录失败', 'error');
                btn.disabled = false;
                btn.textContent = '登 录';
            }
        });

        function showMessage(text, type) {
            messageEl.textContent = text;
            messageEl.className = `message ${type}`;
            messageEl.style.display = 'block';
        }

        function hideMessage() { messageEl.style.display = 'none'; }
    </script>
</body>
</html>

4.6 案例六:文件上传(POST + FormData)

流程图

成功
失败
选择文件
点击上传按钮
创建FormData对象
调用axios.post发送FormData
显示上传进度
上传完成?
显示文件信息
显示错误信息

前端HTML

html 复制代码
<!-- public/06-upload.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>文件上传 - POST请求</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { font-family: 'Segoe UI', sans-serif; background: #f5f7fa; padding: 20px; }
        .container { max-width: 600px; margin: 0 auto; }
        h1 { color: #1a1a2e; margin-bottom: 20px; text-align: center; }
        .card { background: white; border-radius: 12px; padding: 24px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
        .method-badge { display: inline-block; background: #8b5cf6; color: white; padding: 4px 12px; border-radius: 4px; font-size: 12px; font-weight: bold; }
        .upload-area { border: 2px dashed #d1d5db; border-radius: 12px; padding: 40px; text-align: center; cursor: pointer; transition: all 0.2s; }
        .upload-area:hover { border-color: #8b5cf6; background: #faf5ff; }
        .upload-area.dragover { border-color: #8b5cf6; background: #f3e8ff; }
        .upload-icon { font-size: 48px; margin-bottom: 12px; }
        .upload-text { color: #6b7280; }
        .upload-text strong { color: #8b5cf6; }
        input[type="file"] { display: none; }
        .btn { width: 100%; padding: 14px; background: #8b5cf6; color: white; border: none; border-radius: 8px; font-size: 16px; font-weight: 600; cursor: pointer; margin-top: 16px; transition: background 0.2s; }
        .btn:hover { background: #7c3aed; }
        .btn:disabled { background: #d1d5db; cursor: not-allowed; }
        .progress-container { margin-top: 20px; display: none; }
        .progress-bar-bg { height: 8px; background: #e5e7eb; border-radius: 4px; overflow: hidden; }
        .progress-bar { height: 100%; background: linear-gradient(90deg, #8b5cf6, #a78bfa); width: 0; transition: width 0.3s; }
        .progress-text { margin-top: 8px; font-size: 14px; color: #6b7280; text-align: center; }
        .file-info { margin-top: 20px; padding: 16px; background: #f9fafb; border-radius: 8px; display: none; }
        .file-info.show { display: block; }
        .file-item { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid #e5e7eb; }
        .file-item:last-child { border-bottom: none; }
        .file-name { font-weight: 500; color: #374151; }
        .file-size { color: #6b7280; }
    </style>
</head>
<body>
    <div class="container">
        <h1>文件上传 <span class="method-badge">POST</span></h1>
        <div class="card">
            <div class="upload-area" id="uploadArea">
                <div class="upload-icon">📁</div>
                <p class="upload-text">将文件拖拽到此处,或 <strong>点击选择文件</strong></p>
                <p class="upload-text" style="font-size:12px;margin-top:8px;">支持 jpg、png、pdf,大小不超过 10MB</p>
            </div>
            <input type="file" id="fileInput" accept=".jpg,.jpeg,.png,.pdf">
            <div id="selectedFile" style="margin-top:16px;padding:12px;background:#f3f4f6;border-radius:8px;display:none;">
                <strong>已选择:</strong><span id="fileName"></span>
            </div>
            <button class="btn" id="uploadBtn" disabled>上传文件</button>
            <div class="progress-container" id="progressContainer">
                <div class="progress-bar-bg">
                    <div class="progress-bar" id="progressBar"></div>
                </div>
                <div class="progress-text" id="progressText">0%</div>
            </div>
            <div class="file-info" id="fileInfo">
                <h3 style="margin-bottom:12px;">上传成功!</h3>
                <div id="fileDetails"></div>
            </div>
        </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/axios@1.15.0/dist/axios.min.js"></script>
    <script>
        const API_BASE = 'http://localhost:3000/api';
        let selectedFile = null;
        const uploadArea = document.getElementById('uploadArea');
        const fileInput = document.getElementById('fileInput');
        const uploadBtn = document.getElementById('uploadBtn');
        const progressContainer = document.getElementById('progressContainer');
        const progressBar = document.getElementById('progressBar');
        const progressText = document.getElementById('progressText');
        const fileInfo = document.getElementById('fileInfo');
        const fileDetails = document.getElementById('fileDetails');
        const selectedFileEl = document.getElementById('selectedFile');
        const fileNameEl = document.getElementById('fileName');

        uploadArea.addEventListener('click', () => fileInput.click());
        uploadArea.addEventListener('dragover', (e) => { e.preventDefault(); uploadArea.classList.add('dragover'); });
        uploadArea.addEventListener('dragleave', () => { uploadArea.classList.remove('dragover'); });
        uploadArea.addEventListener('drop', (e) => { e.preventDefault(); uploadArea.classList.remove('dragover'); const file = e.dataTransfer.files[0]; if (file) handleFileSelect(file); });
        fileInput.addEventListener('change', (e) => { const file = e.target.files[0]; if (file) handleFileSelect(file); });

        function handleFileSelect(file) {
            if (file.size > 10 * 1024 * 1024) { alert('文件大小不能超过10MB'); return; }
            selectedFile = file;
            fileNameEl.textContent = `${file.name} (${formatFileSize(file.size)})`;
            selectedFileEl.style.display = 'block';
            uploadBtn.disabled = false;
            fileInfo.classList.remove('show');
        }

        uploadBtn.addEventListener('click', async () => {
            if (!selectedFile) return;
            const formData = new FormData();
            formData.append('file', selectedFile);
            uploadBtn.disabled = true;
            uploadBtn.textContent = '上传中...';
            progressContainer.style.display = 'block';
            progressBar.style.width = '0%';
            progressText.textContent = '0%';
            try {
                const res = await axios.post(`${API_BASE}/upload`, formData, {
                    headers: { 'Content-Type': 'multipart/form-data' },
                    onUploadProgress: (progressEvent) => {
                        const percent = Math.round((progressEvent.loaded / progressEvent.total) * 100);
                        progressBar.style.width = `${percent}%`;
                        progressText.textContent = `${percent}%`;
                    }
                });
                progressContainer.style.display = 'none';
                uploadBtn.disabled = false;
                uploadBtn.textContent = '上传文件';
                fileDetails.innerHTML = `
                    <div class="file-item"><span class="file-name">文件名</span><span class="file-size">${res.data.data.originalName}</span></div>
                    <div class="file-item"><span class="file-name">保存名称</span><span class="file-size">${res.data.data.filename}</span></div>
                    <div class="file-item"><span class="file-name">文件大小</span><span class="file-size">${formatFileSize(res.data.data.size)}</span></div>
                `;
                fileInfo.classList.add('show');
            } catch (err) {
                alert(`上传失败: ${err.response?.data?.error || err.message}`);
                uploadBtn.disabled = false;
                uploadBtn.textContent = '上传文件';
                progressContainer.style.display = 'none';
            }
        });

        function formatFileSize(bytes) {
            if (bytes < 1024) return bytes + ' B';
            if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
            return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
        }
    </script>
</body>
</html>

第五章:Axios高级特性

5.1 Axios实例与拦截器

拦截器流程图

响应拦截器
请求拦截器
请求配置
请求拦截器1 - 添加Token
请求拦截器2 - 防缓存
发送请求到服务器
响应拦截器1 - 统一返回data
响应拦截器2 - 错误处理
返回最终响应

完整Axios封装

javascript 复制代码
// public/js/axiosClient.js
const request = axios.create({
    baseURL: 'http://localhost:3000/api',
    timeout: 15000,
    headers: { 'Content-Type': 'application/json' }
});

request.interceptors.request.use(
    (config) => {
        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) => Promise.reject(error)
);

request.interceptors.response.use(
    (response) => response.data,
    (error) => {
        if (error.response) {
            const { status } = error.response;
            switch (status) {
                case 401:
                    localStorage.removeItem('token');
                    window.location.href = '/05-login.html';
                    break;
                case 404:
                    return Promise.reject(new Error('资源不存在'));
                case 500:
                    return Promise.reject(new Error('服务器错误'));
            }
        } else if (error.request) {
            return Promise.reject(new Error('网络连接失败'));
        }
        return Promise.reject(error);
    }
);

export default request;

5.2 取消请求(AbortController)

取消请求流程图

发起请求
创建AbortController
将signal传递给axios
用户取消操作
调用controller.abort
请求被中断

示例代码

javascript 复制代码
let abortController = null;

async function searchUsers(keyword) {
    if (abortController) abortController.abort();
    abortController = new AbortController();
    try {
        const res = await axios.get('/api/users', {
            params: { search: keyword },
            signal: abortController.signal
        });
        console.log(res.data);
    } catch (err) {
        if (axios.isCancel(err)) console.log('请求已取消');
        else console.error('请求失败:', err);
    }
}

function cancelSearch() {
    if (abortController) abortController.abort('用户取消搜索');
}

5.3 并发请求(Promise.all)

并发请求流程图

服务器 Axios 浏览器 服务器 Axios 浏览器 par 并行请求 par 并行响应 Promise.all(getUsers, getPosts, getComments) GET /api/users GET /api/posts GET /api/comments users数据 posts数据 comments数据 users, posts, comments

示例代码

javascript 复制代码
async function fetchAllData() {
    try {
        const [users, posts, comments] = await Promise.all([
            axios.get('http://localhost:3000/api/users'),
            axios.get('http://localhost:3000/api/posts'),
            axios.get('http://localhost:3000/api/comments')
        ]);
        console.log('用户:', users.data);
        console.log('文章:', posts.data);
        console.log('评论:', comments.data);
    } catch (err) {
        console.error('并发请求失败:', err);
    }
}

第七章:最佳实践与项目总结

7.1 完整项目结构

数据层
Express服务器
public/ 静态文件目录
01-get-users.html
02-create-user.html
03-update-user.html
04-delete-user.html
05-login.html
06-upload.html
server.js 入口
users 数组
uploads/ 文件夹

7.2 运行项目

bash 复制代码
# 1. 进入项目目录
cd axios-express-tutorial

# 2. 安装依赖
npm install

# 3. 启动服务器
npm start

# 4. 访问页面
# http://localhost:3000/01-get-users.html
# http://localhost:3000/02-create-user.html
# http://localhost:3000/03-update-user.html
# http://localhost:3000/04-delete-user.html
# http://localhost:3000/05-login.html
# http://localhost:3000/06-upload.html

7.3 技术栈总结

分类 技术 版本
前端请求 Axios 1.15.0
后端框架 Express 5.2.1
Node.js JavaScript ES6+
文件上传 Multer latest
跨域处理 CORS latest

第六章:Axios高级配置

6.1 全局配置与数据转换

全局默认配置

javascript 复制代码
// 设置全局默认配置
axios.defaults.baseURL = 'https://api.example.com';
axios.defaults.timeout = 10000;
axios.defaults.headers.common['Authorization'] = 'Bearer token';
axios.defaults.headers.post['Content-Type'] = 'application/json';

// 使用全局配置发送请求
const res = await axios.get('/users');  // 自动使用 baseURL

请求/响应数据转换

javascript 复制代码
const instance = axios.create({
    // 请求数据转换 - 发送前修改数据
    transformRequest: [function (data, headers) {
        // 例如:加密敏感数据
        if (data.password) {
            data.password = btoa(data.password);  // Base64编码示例
        }
        return JSON.stringify(data);
    }],
    
    // 响应数据转换 - 接收后修改数据
    transformResponse: [function (data) {
        // 例如:统一处理日期格式
        const parsed = JSON.parse(data);
        if (parsed.createdAt) {
            parsed.createdAt = new Date(parsed.createdAt).toLocaleString();
        }
        return parsed;
    }]
});

6.2 响应类型与进度监控

响应类型配置

javascript 复制代码
// JSON(默认)
const jsonRes = await axios.get('/api/data');

// Blob - 用于文件下载
const blobRes = await axios.get('/api/image', {
    responseType: 'blob'
});
const imageUrl = URL.createObjectURL(blobRes.data);

// ArrayBuffer - 用于二进制数据处理
const bufferRes = await axios.get('/api/binary', {
    responseType: 'arraybuffer'
});

// Text - 纯文本
const textRes = await axios.get('/api/text', {
    responseType: 'text'
});

上传进度

javascript 复制代码
const formData = new FormData();
formData.append('file', fileInput.files[0]);

const res = await axios.post('/upload', formData, {
    headers: { 'Content-Type': 'multipart/form-data' },
    onUploadProgress: (progressEvent) => {
        const percent = Math.round(
            (progressEvent.loaded * 100) / progressEvent.total
        );
        console.log(`上传进度: ${percent}%`);
        progressBar.style.width = `${percent}%`;
    }
});

下载进度

javascript 复制代码
const res = await axios.get('/download/large-file', {
    responseType: 'blob',
    onDownloadProgress: (progressEvent) => {
        const percent = Math.round(
            (progressEvent.loaded * 100) / progressEvent.total
        );
        console.log(`下载进度: ${percent}%`);
    }
});

// 创建下载链接
const url = window.URL.createObjectURL(new Blob([res.data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', 'file.zip');
document.body.appendChild(link);
link.click();
link.remove();

6.3 跨域与安全配置

javascript 复制代码
// JSON(默认)
const jsonRes = await axios.get('/api/data');

// Blob - 用于文件下载
const blobRes = await axios.get('/api/image', {
    responseType: 'blob'
});
const imageUrl = URL.createObjectURL(blobRes.data);

// ArrayBuffer - 用于二进制数据处理
const bufferRes = await axios.get('/api/binary', {
    responseType: 'arraybuffer'
});

// Text - 纯文本
const textRes = await axios.get('/api/text', {
    responseType: 'text'
});

// Stream - 流式数据(Node.js环境)
const streamRes = await axios.get('/api/stream', {
    responseType: 'stream'
});

跨域凭证配置

javascript 复制代码
// 允许跨域请求携带 Cookie
const instance = axios.create({
    withCredentials: true,  // 默认 false
    baseURL: 'https://api.example.com'
});

// 服务器端需要配合设置 CORS
// Access-Control-Allow-Credentials: true
// Access-Control-Allow-Origin: http://localhost:3000 (不能为 *)

状态码验证

javascript 复制代码
const instance = axios.create({
    // 自定义哪些状态码应该触发错误
    validateStatus: function (status) {
        // 默认:status >= 200 && status < 300
        // 自定义:接受 200-400 的状态码
        return status >= 200 && status < 400;
    }
});

HTTP基础认证

javascript 复制代码
// Basic Auth 认证
const res = await axios.get('/api/protected', {
    auth: {
        username: 'admin',
        password: 'secret123'
    }
});

6.4 其他高级功能

URL参数序列化

javascript 复制代码
const res = await axios.get('/api/search', {
    params: {
        keywords: ['javascript', 'axios'],
        filters: { date: '2024', type: 'article' }
    },
    paramsSerializer: {
        encode: (param) => encodeURIComponent(param),
        serialize: (params) => {
            return Object.keys(params)
                .map(key => `${key}=${JSON.stringify(params[key])}`)
                .join('&');
        },
        indexes: null
    }
});

代理配置(Node.js)

javascript 复制代码
const res = await axios.get('https://api.example.com/data', {
    proxy: {
        protocol: 'https',
        host: '127.0.0.1',
        port: 9000,
        auth: {
            username: 'proxy-user',
            password: 'proxy-pass'
        }
    }
});

自定义适配器

javascript 复制代码
// 使用 Fetch API 适配器(Axios 1.x 支持)
const instance = axios.create({
    adapter: 'fetch'  // 使用浏览器原生 fetch
});

// 或自定义适配器
const instance = axios.create({
    adapter: async (config) => {
        // 自定义请求逻辑
        return {
            data: {},
            status: 200,
            statusText: 'OK',
            headers: {},
            config,
            request: {}
        };
    }
});

请求方法别名补充

javascript 复制代码
// 除了常用的 get/post/put/patch/delete

// HEAD 请求 - 只获取响应头
const headRes = await axios.head('/api/resource');
console.log(headRes.headers['content-length']);

// OPTIONS 请求 - 获取服务器支持的HTTP方法
const optionsRes = await axios.options('/api/resource');
console.log(optionsRes.headers['allow']);

// 通用 request 方法
const res = await axios.request({
    method: 'GET',
    url: '/api/users',
    params: { page: 1 }
});

速率限制(Node.js)

javascript 复制代码
// 限制上传/下载速率
const res = await axios.post('/upload', largeData, {
    maxRate: [100 * 1024, 100 * 1024]  // 每秒100KB
});

第七章:Axios最佳实践与项目总结

7.1 错误处理最佳实践

javascript 复制代码
try {
    const res = await axios.get('/api/data');
} catch (error) {
    if (axios.isAxiosError(error)) {
        // Axios 错误
        console.log('错误类型:', error.name);
        console.log('错误消息:', error.message);
        console.log('请求配置:', error.config);
        
        if (error.response) {
            // 服务器响应了错误状态码
            console.log('状态码:', error.response.status);
            console.log('响应数据:', error.response.data);
            console.log('响应头:', error.response.headers);
        } else if (error.request) {
            // 请求已发送但没有收到响应
            console.log('请求对象:', error.request);
        } else {
            // 请求配置出错
            console.log('配置错误:', error.message);
        }
        
        // 超时错误
        if (error.code === 'ECONNABORTED') {
            console.log('请求超时');
        }
        
        // 网络错误
        if (error.code === 'ERR_NETWORK') {
            console.log('网络错误');
        }
    } else {
        // 非 Axios 错误
        console.log('未知错误:', error);
    }
}

7.2 封装 Axios 实例

javascript 复制代码
// utils/request.js
import axios from 'axios';
import { getToken, removeToken } from './auth';

const request = axios.create({
    baseURL: import.meta.env.VITE_API_BASE_URL,
    timeout: 15000,
    headers: {
        'Content-Type': 'application/json'
    }
});

// 请求拦截器
request.interceptors.request.use(
    (config) => {
        const token = getToken();
        if (token) {
            config.headers.Authorization = `Bearer ${token}`;
        }
        
        // 添加时间戳防止缓存
        if (config.method === 'get') {
            config.params = {
                ...config.params,
                _t: Date.now()
            };
        }
        
        return config;
    },
    (error) => Promise.reject(error)
);

// 响应拦截器
request.interceptors.response.use(
    (response) => {
        // 直接返回数据
        return response.data;
    },
    (error) => {
        const { response } = error;
        
        if (response?.status === 401) {
            removeToken();
            window.location.href = '/login';
        }
        
        // 统一错误提示
        const message = response?.data?.message || '请求失败';
        console.error(message);
        
        return Promise.reject(error);
    }
);

export default request;

7.3 API 模块化管理

javascript 复制代码
// api/user.js
import request from '@/utils/request';

export const userApi = {
    getList: (params) => request.get('/users', { params }),
    getById: (id) => request.get(`/users/${id}`),
    create: (data) => request.post('/users', data),
    update: (id, data) => request.put(`/users/${id}`, data),
    delete: (id) => request.delete(`/users/${id}`),
    uploadAvatar: (file) => {
        const formData = new FormData();
        formData.append('file', file);
        return request.post('/users/avatar', formData, {
            headers: { 'Content-Type': 'multipart/form-data' }
        });
    }
};

// 使用
import { userApi } from '@/api/user';
const users = await userApi.getList({ page: 1, size: 10 });

7.4 请求重试机制

javascript 复制代码
// 带重试的请求
async function requestWithRetry(config, maxRetries = 3) {
    let lastError;
    
    for (let i = 0; i < maxRetries; i++) {
        try {
            return await axios(config);
        } catch (error) {
            lastError = error;
            
            // 只在网络错误或5xx错误时重试
            if (!error.response || error.response.status >= 500) {
                const delay = Math.pow(2, i) * 1000;  // 指数退避
                console.log(`第${i + 1}次重试,等待${delay}ms...`);
                await new Promise(resolve => setTimeout(resolve, delay));
            } else {
                throw error;  // 4xx错误不重试
            }
        }
    }
    
    throw lastError;
}

7.5 请求缓存

javascript 复制代码
// 简单的请求缓存实现
const cache = new Map();

async function cachedRequest(config, ttl = 60000) {
    const key = JSON.stringify(config);
    const cached = cache.get(key);
    
    if (cached && Date.now() - cached.time < ttl) {
        console.log('使用缓存数据');
        return cached.data;
    }
    
    const res = await axios(config);
    cache.set(key, { data: res, time: Date.now() });
    return res;
}
相关推荐
禁止摆烂_才浅4 天前
Axios 完整封装合集(鉴权 + 重复拦截 + Loading + 缓存 + 统一错误 + 请求重试|全代码逐行注释)
前端·javascript·axios
艾伦野鸽ggg21 天前
前端异步请求的状态竞争(请求竞态)问题
前端·javascript·axios
触底反弹1 个月前
🔐 前端鉴权不再难!手把手带你搞懂 JWT + Zustand + Axios 的完整登录方案
前端·axios
BreezeJiang1 个月前
JWT 登录不是存个 token:React 鉴权真正要闭合的是一条数据流
javascript·react.js·axios
禁止摆烂_才浅1 个月前
Axios 高频面试题
前端·面试·axios
弹简特2 个月前
【Vue3速成】07-vue3异步请求之Axios-01
vue·axios
古韵2 个月前
axios 已经跑得好好的,还要不要上 alova?
前端·javascript·axios
目送卷王离开2 个月前
前端接 LLM 流式输出:EventSource、axios、fetch 怎么选
axios
shawxlee2 个月前
vue3+axios挑战最简洁实用的配置封装+接口调用:请求拦截器、响应拦截器、通用请求方法(单个请求/并发请求/下载文件)、统一接口管理
前端·经验分享·vue·接口·axios·api
mqcode3 个月前
你项目里的 axios,封对了吗?从裸用到生产级的四步进化
vue.js·axios