第 10 节:异步请求与 Axios —— 前端与后端

第 10 节:异步请求与 Axios ------ 前端与后端的对话

前面的章节已经让我们能够构建交互丰富的界面,但数据始终存储在浏览器本地(第 9 节)或硬编码在脚本中。真正的生产系统里,数据来自后端 API。本节将引入 Axios,一个基于 Promise 的 HTTP 客户端,让你用熟悉的"请求-响应"模式与服务器通信,并为后续的 Vue.js 项目打下基础。


1. 为什么需要 Axios?

原生 JavaScript 提供了 fetch 方法发送 HTTP 请求,但它存在一些不便之处:需要手动解析 JSON、无法自动处理 4xx/5xx 状态码、缺乏拦截器机制。Axios 补足了这些短板,并且成为 Vue、React 等框架的标配 HTTP 库。

作为后端开发者,你可以直接把它看作 前端版的 RestTemplate / HttpClient:发送请求,接收响应,自动序列化 / 反序列化 JSON。

Axios 的三大省心特性

  1. 自动 JSON 转换 :传对象作为请求体时,自动序列化为 JSON 字符串并设置 Content-Type: application/json;响应数据会自动解析为对象。
  2. 错误即抛异常 :任何非 2xx 的状态码(如 400、404、500)都会让 Promise 进入 rejected 状态,直接进入 catch,无需手动判断 response.ok
  3. 拦截器:可在请求发出前或响应返回后统一添加逻辑(如附加 Token、全局 Loading 效果、错误提示),类似于后端的 Filter 或 AOP。

2. 核心前置知识:async / await / try-catch

发送网络请求是异步操作:代码不能卡住页面等待数据返回。现代 JavaScript 使用 async / await 语法让异步代码看起来像同步代码。

2.1 await:等待一个 Promise

await 会暂停当前函数的执行,直到后面的 Promise 完成(成功或失败)。对于 Axios,它返回的正是 Promise。

javascript 复制代码
const response = await axios.get('/api/students');   // 等待请求完成
console.log(response.data);                          // 这一行在数据返回之后才执行

类比:就像餐厅点餐后站在柜台等叫号,拿到餐后才离开。

2.2 async:标记异步函数

任何使用 await 的函数都必须用 async 声明,否则会报语法错误。

javascript 复制代码
async function fetchStudents() {
    const res = await axios.get('/api/students');
    return res.data;
}

async 函数本身的返回值也会被包装成 Promise。

2.3 try-catch:捕获错误

网络请求可能失败(断网、超时、服务器异常),必须用 try-catch 捕获异常,避免未处理的 Promise 拒绝导致页面崩溃。

javascript 复制代码
async function loadData() {
    try {
        const res = await axios.get('/api/students');
        render(res.data);
    } catch (error) {
        // error.response 仅在服务器返回响应时存在
        const msg = error.response?.data?.message || '网络故障,请稍后重试';
        showError(msg);
    }
}

后端对照 :这就是你在 Java 里用 try-catch 包裹 HTTP 调用,并从 HttpClientErrorException 中提取错误信息的模式。


3. Axios 基础用法

3.1 引入 Axios

如果使用 npm 管理依赖,通常通过 npm install axios 安装并 import。在简单的静态页面中,可以直接使用 CDN 或下载好的本地文件:

html 复制代码
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<!-- 或者使用本地文件 -->
<script src="vendor/axios.min.js"></script>

引入后,全局变量 axios 可用。

3.2 发送各类请求

GET 请求(查询列表或单个资源):

javascript 复制代码
const res = await axios.get('/api/students');
console.log(res.status);    // 200
console.log(res.data);      // 直接是数组,无需 JSON.parse

GET 带参数

javascript 复制代码
const res = await axios.get('/api/students', { params: { name: '张三' } });
// 实际请求地址:/api/students?name=张三

POST 请求(新增资源):

javascript 复制代码
const newStudent = { name: '王小明', class: '2班', age: 20 };
const res = await axios.post('/api/students', newStudent);
// 请求体自动序列化为 JSON,Content-Type 自动设为 application/json

PUT / PATCH 请求(更新资源):

javascript 复制代码
// PUT 通常用于完整替换,PATCH 用于部分更新(本例使用 PUT)
const res = await axios.put(`/api/students/${studentId}`, updatedStudent);

DELETE 请求

javascript 复制代码
const res = await axios.delete(`/api/students/${studentId}`);
// 成功时状态码通常是 204,res.data 为空

3.3 响应结构

Axios 的响应对象 res 包含以下关键属性:

  • res.data:后端返回的数据体(通常就是我们需要的内容)。
  • res.status:HTTP 状态码(200、201、204、404、500 等)。
  • res.statusText:状态文本(如 "OK")。
  • res.headers:响应头对象。
  • res.config:本次请求的配置信息。

3.4 错误处理细节

当请求失败(网络错误或状态码非 2xx)时,catch 中的 error 对象包含:

  • error.response:有响应时的 Response 对象,从中可读取 error.response.statuserror.response.data
  • error.request:已发送请求但未收到响应(如超时)。
  • error.message:通用的错误描述。

常见处理模式:

javascript 复制代码
catch (error) {
    if (error.response) {
        // 服务器返回了 4xx 或 5xx
        console.error('状态码:', error.response.status);
        console.error('错误信息:', error.response.data.message);
    } else if (error.request) {
        // 请求已发出但没收到响应(网络断开等)
        console.error('网络连接异常');
    } else {
        // 其他错误
        console.error('请求配置错误', error.message);
    }
}

4. 实战:学生管理 CRUD 与数据流闭环

结合之前学习的 DOM 渲染(第 6 节)、事件处理(第 7 节)、表单校验(第 8 节),我们可以构建一个完整的学生管理系统,通过 Axios 对接 RESTful 后端。

假设后端运行在 http://localhost:3000,提供以下 API:

  • GET /api/students --- 获取全部学生列表
  • POST /api/students --- 新增学生
  • PUT /api/students/:id --- 修改学生
  • DELETE /api/students/:id --- 删除学生

核心代码骨架如下(以获取列表和新增为例):

javascript 复制代码
// 渲染表格(复用第6节 DOM 操作)
function renderTable(students) {
    const tbody = document.getElementById('student-tbody');
    tbody.innerHTML = students.map(s => `
        <tr data-id="${s.id}">
            <td>${s.id}</td>
            <td>${s.name}</td>
            <td>${s.class}</td>
            <td>${s.age}</td>
            <td>
                <button data-action="edit">编辑</button>
                <button data-action="delete">删除</button>
            </td>
        </tr>
    `).join('');
}

// 获取全部学生
async function loadStudents() {
    try {
        const res = await axios.get('http://localhost:3000/api/students');
        renderTable(res.data);
    } catch (error) {
        alert('加载学生失败:' + (error.response?.data?.message || error.message));
    }
}

// 新增学生(表单提交事件处理)
document.getElementById('studentForm').addEventListener('submit', async (e) => {
    e.preventDefault();  // 阻止页面刷新(第7节)
    const name = document.getElementById('name').value.trim();
    if (!name) return alert('姓名不能为空');  // 第8节基础校验

    try {
        await axios.post('http://localhost:3000/api/students', { name, class: '1班', age: 18 });
        await loadStudents();   // 重新获取最新列表 ------ "写完后重读"闭环
    } catch (error) {
        alert('新增失败');
    }
});

// 事件委托处理编辑和删除(第7节)
document.getElementById('student-tbody').addEventListener('click', async (e) => {
    const row = e.target.closest('tr');
    const id = row?.dataset.id;
    if (!id) return;

    if (e.target.dataset.action === 'delete') {
        try {
            await axios.delete(`http://localhost:3000/api/students/${id}`);
            await loadStudents();
        } catch (error) {
            alert('删除失败');
        }
    }
    // 编辑逻辑类似,先填充表单,再发 PUT 请求...
});

// 页面启动时加载数据
loadStudents();

数据流闭环 :任何一次增、删、改操作成功后,都重新调用 loadStudents() 获取最新列表并刷新渲染。这种模式简单可靠,是管理后台最常见的做法(后续会引入状态管理优化性能,但本质思想不变)。


5. 拦截器:统一处理横切关注点

当应用中的请求越来越多,你会在每个 catch 里重复写错误提示,或在每个请求前手动开启 Loading。Axios 的拦截器可以像后端的 Filter 一样,在请求发送前或响应返回后统一插入逻辑。

5.1 创建 Axios 实例

为避免污染全局 axios 对象,通常先通过 axios.create 创建一个带有默认配置的实例:

javascript 复制代码
const apiClient = axios.create({
    baseURL: 'http://localhost:3000',   // 基础路径
    timeout: 5000                        // 超时时间
});

后续所有请求都通过这个 apiClient 发送。

5.2 请求拦截器

在请求发出前,可以在此附加 Token、显示全局 Loading 等:

javascript 复制代码
apiClient.interceptors.request.use(config => {
    // 从 localStorage(第9节)取出登录令牌附加到请求头
    const token = localStorage.getItem('auth_token');
    if (token) {
        config.headers.Authorization = `Bearer ${token}`;
    }
    // 显示 loading 动画
    document.getElementById('spinner').hidden = false;
    return config;   // 必须返回 config,否则请求不会发出
}, error => {
    return Promise.reject(error);
});

5.3 响应拦截器

在数据返回后,先于业务代码得到处理。常用于全局错误处理、关闭 Loading、数据格式统一等。

javascript 复制代码
apiClient.interceptors.response.use(
    response => {
        document.getElementById('spinner').hidden = true;
        // 可选:直接返回 response.data,让业务代码少写一层 .data
        return response;
    },
    error => {
        document.getElementById('spinner').hidden = true;
        // 统一错误提示(如 toast)
        const msg = error.response?.data?.message || '网络连接失败';
        showGlobalError(msg);
        return Promise.reject(error);   // 继续抛出,以便业务 catch 可再次处理
    }
);

现在,业务代码只需调用 apiClient.get(...),Token、Loading、错误提示全部自动注入,不再散落在各处。


6. 封装 API 模块:为 Vue 项目铺路

在一个较大的前端工程中,我们不会在各个组件里直接写 axios.get。更规范的做法是将 API 调用封装成独立的模块,按资源划分文件。

例如 api/studentApi.js

javascript 复制代码
import apiClient from './apiClient';  // 上一节创建的 axios 实例

export function getStudents(params) {
    return apiClient.get('/students', { params });
}

export function getStudentById(id) {
    return apiClient.get(`/students/${id}`);
}

export function createStudent(data) {
    return apiClient.post('/students', data);
}

export function updateStudent(id, data) {
    return apiClient.put(`/students/${id}`, data);
}

export function deleteStudent(id) {
    return apiClient.delete(`/students/${id}`);
}

在页面或组件中使用时:

javascript 复制代码
import { getStudents, createStudent, deleteStudent } from './api/studentApi';

async function load() {
    const res = await getStudents();
    render(res.data);
}

好处

  • 所有 API 地址集中管理,修改后端 URL 只需改 apiClientbaseURL 一处。
  • 业务逻辑与网络请求解耦,代码更干净。
  • 易于进行单元测试和 mock。

这种组织方式正是 Vue、React 等项目中 api/ 目录的标准形态,提前熟悉该模式能让你无缝过渡到框架开发。


7. 小结

  • Axios 优势:自动 JSON 解析、错误即异常、拦截器机制。
  • 异步三件套async 函数内使用 await 等待 Promise,try-catch 捕获网络异常。
  • 基本请求GET/POST/PUT/DELETE,数据都在 res.data 中。
  • 数据流闭环:增删改后重新获取列表,保持界面与后端同步。
  • 拦截器:请求拦截(加 Token、Loading),响应拦截(统一错误提示、数据解包)。
  • 模块封装:将 axios 实例和 API 方法独立模块化,提高可维护性。

掌握了 Axios,你就打通了前端与后端的数据通道。并最终接入 Vue.js 的响应式体系。

相关推荐
Coder_Ke1 小时前
源码没错,API 却“失踪”了:微信开发者工具的一次作用域背刺
前端
渣波1 小时前
从“迷路”到“瞬移”:彻底搞懂 React Router 的 `useLocation` 与 `useNavigate`
前端·javascript
申君健24864182772021 小时前
WebGPU 开发入门:从设备初始化到渲染一帧
前端
橘子星1 小时前
React Context + 自定义 Hooks:告别 Props 地狱,一行代码跨层级传数据
前端·javascript
moMo1 小时前
前端路由,其实就是三个对象在演戏
前端·react.js
用户2930750976691 小时前
React 自定义 Hooks 实战:用 useMouse 理解响应式封装
前端
触底反弹1 小时前
🔥 别再只用 useRef 拿 DOM 了!这 5 个实战场景让你彻底理解它(附源码解析 + 面试题)
前端·javascript·react.js
玉宇夕落1 小时前
useRef + Web Worker —— 从零到一理解React多线程协作原理
前端
用户6919026813391 小时前
React useRef、useEffect、useState 与 Web Worker 多线程实践
前端