第 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 的三大省心特性:
- 自动 JSON 转换 :传对象作为请求体时,自动序列化为 JSON 字符串并设置
Content-Type: application/json;响应数据会自动解析为对象。 - 错误即抛异常 :任何非 2xx 的状态码(如 400、404、500)都会让 Promise 进入
rejected状态,直接进入catch,无需手动判断response.ok。 - 拦截器:可在请求发出前或响应返回后统一添加逻辑(如附加 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.status和error.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 只需改
apiClient的baseURL一处。 - 业务逻辑与网络请求解耦,代码更干净。
- 易于进行单元测试和 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 的响应式体系。