目录
-
- 一,AJAX是什么
- 二,使用 Axios 来实现 AJAX 请求
-
- 1.和fetch的区别
- 2.下载Axios
- 3.使用Axios
- 4.Axios 常用方法速查表
- 5.Promise常用方法速查表
- 6.完整示例
- 三,关于进阶的内容
-
- 1.拦截器(Interceptors)
- 2. 取消请求(CancelToken / AbortController)
- 3. 防范 XSS / CSRF 安全攻击
- 4. 上传与下载的进度监听(Progress Events)
- 5. 错误对象的层级划分(AxiosError)
一,AJAX是什么
AJAX 是 Asynchronous JavaScript and XML(异步 JavaScript 和 XML)的缩写。
它的核心作用是:让网页在不重新加载整个页面的情况下,向服务器发送请求并局部更新页面。
比如在百度的搜索框中,我们输一个字下方就会立马弹出一串联想词,但是这并不是直接刷新整个页面,而是AJAX在后台随着输入,不断向服务器请求匹配,局部刷新下面那一串联想词。
AJAX实际是以下几种技术的组合:
工作流程:
[ 网页触发事件 ] (如点击按钮)
│
▼
[ 创建请求并发送 ] (JavaScript 在后台向服务器发送请求)
│
▼
[ 服务器处理 ] (服务器收到请求,提取数据,只返回需要的数据,如 JSON)
│
▼
[ 局部更新网页 ] (JavaScript 收到数据,更新网页的某一部分)
二,使用 Axios 来实现 AJAX 请求
Axios 是一个基于 Promise 的 HTTP 客户端,主要用于在浏览器和 Node.js 环境中发送异步 HTTP 请求(如 GET、POST、PUT、DELETE 等)。
简单来说,它是一个封装好的 JavaScript 库,让你能够更方便地与后端 API 进行通信。
Axios 是 AJAX 技术的一种具体实现工具。
1.和fetch的区别
fetch和Axios一样,同样是实现AJAX的一种工具。
fetch 是浏览器自带的“基本款”Promise 式请求 API,Axios 是在它(或 XMLHttpRequest)之上封装的“增强版”库,提供了更多便捷功能。 两者都能实现 AJAX 请求,只是 Axios 用起来更省心。
2.下载Axios
Axios 是一个第三方库,使用前需要先把它“拿”到你的项目里。
方式一:在 HTML 中直接使用 CDN(最快速上手)
html
<!– 在 body 底部或 head 中引入 –>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
引入后,全局就有了 axios 对象。 或者直接下载这个文件,在本地导入,更快捷。
方式二:使用 npm(用于 Node.js 或现代前端工程)
npm install axios
然后在代码中:
const axios = require('axios'); // Node.js (CommonJS)
// 或
import axios from 'axios'; // ES module (Vue/React)
3.使用Axios
下载后,项目中就会多一个axios对象,在浏览器中就是window.axios。
我们本质上是用Axios的方法来‘发请求’。
每个方法对应一种HTTP请求动作。
他们的返回值就是Promise对象,这个对象上有.then()等方法,用于处理拿到数据之后该干什么。
通常像下面这样写:
前端Axios代码:
import axios from 'axios';
// 1. 设置全局的 baseURL(通常放在项目的入口文件,如 main.js 或 index.js 里)
axios.defaults.baseURL = 'http://127.0.0.1:8000';
// 2. 设置全局的超时时间,单位是【毫秒】(1秒 = 1000毫秒)
axios.defaults.timeout = 5000; // 代表 5 秒内服务器没响应就中断并报错
// 3. 之后发请求,直接写相对路径即可
axios.get('/get') // Axios 会自动拼接成 http://127.0.0.1:8000/get
.then(res => console.log(res.data));
axios.post('/items', { name: '商品' }) // 自动拼接成 http://127.0.0.1:8000/items
.then(res => console.log(res.data));
后端FastAPI代码:
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
# 💡 避坑:只要是前后端分离(哪怕都在本地,只要端口不同),就必须加下面这段 CORS 配置!
# 这里配置的是跨域资源共享中间件,用于解决浏览器中AJAX请求的跨域限制问题。
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # 在生产环境建议换成你前端的实际域名/端口
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.get("/get")
def get_data():
return {"message": "成功访问 /get 接口"}
为什么需要那个中间件呢? 浏览器基于同源策略,默认禁止网页向不同域名/端口发送 AJAX 请求。例如: 前端运行在 http://localhost:3000 后端 API 运行在 http://localhost:8000 没有 CORS 配置时,浏览器会拦截请求并报错。加上这段代码后,后端会告诉浏览器“允许跨域访问”,从而请求可以正常进行。
4.Axios 常用方法速查表
| axios.get(url, config) | GET | 获取数据 / 查询 | config 配置项(如 params 查询参数) | 无 | def get_data(id: int) (Query 或 Path) |
| axios.post(url, data, config) | POST | 提交数据 / 新建资源 | data(要发送的 JSON 对象或 Form) | config 配置项(可省略) | def create(item: BaseModel) (Body) |
| axios.put(url, data, config) | PUT | 更新数据(完整替换) | data(更新后的完整对象) | config 配置项(可省略) | def update(item: BaseModel) (Body) |
| axios.patch(url, data, config) | PATCH | 局部修改数据(只改部分字段) | data(需要修改的局部字段) | config 配置项(可省略) | def patch(item: PartialModel) (Body) |
| axios.delete(url, config) | DELETE | 删除数据 | config 配置项(通常把 ID 拼在 URL 里) | 无 | def delete_data(id: int) (Path) |
还有以下写法。
// 这种写法下,所有配置都在同一个对象里,不用去记参数的第几位是什么
axios({
method: 'post', // 请求方式:get, post, put, delete 等
url: '/user/12345', // 相对路径或绝对路径
params: { id: 'query_id' }, // 无论是 POST 还是 GET,Query 参数都写在这里
data: { firstName: 'Fred' }, // 只有 POST/PUT/PATCH 的 Body 数据写在这里
timeout: 3000 // 超时时间
});
5.Promise常用方法速查表
实例方法(非静态方法 / 原型方法) 这类方法必须用在 Promise 实例对象后面,用来链式处理异步操作的最终结果(成功、失败或结束)。
| .then() | 当 Promise 变为 成功(Fulfilled) 时触发 | 一个回调函数,函数的参数是成功返回的数据 | 返回一个新的 Promise(可以继续链式调用) | axios.get('/user').then(res => { console.log(res) }) |
| .catch() | 当 Promise 变为 失败(Rejected) 或代码报错时触发 | 一个回调函数,函数的参数是错误对象(Error) | 返回一个新的 Promise | axios.get('/user').catch(err => { console.error(err) }) |
| .finally() | 无论成功还是失败,只要异步操作结束了就一定会触发 | 一个无参数的回调函数 | 返回当前的 Promise | showLoading(); axios.get('/user').finally(() => { hideLoading() }) |
静态方法(类方法)
这类方法不需要 new 实例,直接通过大写的 Promise 关键字调用,通常用于并发处理多个异步操作(比如同时发多个 Axios 请求)。
| Promise.all(iterable) | 全胜才胜,一败俱败。所有 Promise 都成功才算成功,返回所有结果的数组;只要有一个失败,就立刻整体宣告失败。 | 页面加载时,需要同时获取“用户信息”和“菜单列表”,都拿到了再渲染页面。 | Promise.all([req1, req2]).then(([res1, res2]) => { … }) |
| Promise.allSettled(iterable) | 有始有终,全数等齐。不管成功还是失败,必须等所有异步都执行完,返回一个包含每个任务状态和结果的数组。 | 批量发送邮件或下载文件,最后需要统计“哪些成功了,哪些失败了”。 | Promise.allSettled([req1, req2]).then(results => { … }) |
| Promise.race(iterable) | 谁快听谁,不论好坏。谁的速度最快(不管是成功还是失败),就以谁的结果作为最终结果。 | 给某个异步操作设置超时限制(让你的请求和定时器赛跑)。 | Promise.race([fetchData(), timeout(3000)]) |
| Promise.any(iterable) | 得一胜足矣,全败才败。只要有一个成功就立即算成功,返回那个成功的结果;只有全部都失败,才会宣告失败。 | 从多个不同的镜像服务器/CDN 获取同一个资源,谁先成功返回就用谁的。 | Promise.any([cdn1, cdn2, cdn3]).then(fastestData => { … }) |
| Promise.resolve(value) | 直接创建一个已经是成功状态的 Promise 对象。 | 快速将一个普通数据或第三方数据包装成标准 Promise。 | const p = Promise.resolve('数据'); |
| Promise.reject(reason) | 直接创建一个已经是失败状态的 Promise 对象。 | 在测试或自定义逻辑中,手动抛出一个异步错误。 | const p = Promise.reject(new Error('拒绝访问')); |
6.完整示例
后端fastapi代码
from fastapi import FastAPI, HTTPException, Form
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from typing import Optional
import asyncio
app = FastAPI()
# 允许跨域(本地开发测试必加)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# 模拟数据库
fake_db = {
1: {"name": "iPhone 17 Pro", "price": 8999.0, "status": "active"},
2: {"name": "MacBook Pro M5", "price": 14999.0, "status": "active"}
}
# 1. 对应 Axios.get + Path 参数
@app.get("/items/{item_id}")
async def get_item(item_id: int):
if item_id not in fake_db:
raise HTTPException(status_code=404, detail="商品不存在")
return fake_db[item_id]
# 2. 对应 Axios.get + Query 参数
@app.get("/items")
async def list_items(status: str = "active", limit: int = 10):
# 模拟快速响应
return [item for item in fake_db.values() if item["status"] == status][:limit]
# 模拟一个特别慢的第三方数据源(用于测试比赛/超时)
@app.get("/slow-backup-source")
async def slow_source():
await asyncio.sleep(4) # 故意睡 4 秒
return {"source": "备份服务器", "data": "一些商品元数据"}
# 3. 对应 Axios.post + JSON 请求体
class ItemModel(BaseModel):
name: str
price: float
status: Optional[str] = "active"
@app.post("/items")
async def create_item(item: ItemModel):
new_id = max(fake_db.keys()) + 1
fake_db[new_id] = item.model_dump()
return {"id": new_id, **fake_db[new_id]}
# 4. 对应 Axios.put + 完整修改
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: ItemModel):
if item_id not in fake_db:
raise HTTPException(status_code=404, detail="修改失败,商品不存在")
fake_db[item_id] = item.model_dump()
return {"message": "更新成功", "data": fake_db[item_id]}
# 5. 对应 Axios.delete + 删除
@app.delete("/items/{item_id}")
async def delete_item(item_id: int):
if item_id in fake_db:
del fake_db[item_id]
return {"message": f"成功删除商品 {item_id}"}
raise HTTPException(status_code=404, detail="商品不存在")
前端js代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Axios & Promise 综合实战演练</title>
<!– 引入 Axios CDN –>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background-color: #f5f7fa;
color: #333;
padding: 40px;
max-width: 900px;
margin: 0 auto;
}
h1 { color: #2c3e50; text-align: center; margin-bottom: 30px; }
.card {
background: white;
padding: 24px;
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0,0,0,0.05);
margin-bottom: 20px;
}
h3 { margin-top: 0; color: #34495e; border-left: 4px solid #3498db; padding-left: 10px; }
.btn-group { display: flex; gap: 12px; margin-top: 15px; flex-wrap: wrap; }
button {
background-color: #3498db;
color: white;
border: none;
padding: 10px 20px;
border-radius: 6px;
cursor: pointer;
font-weight: bold;
transition: background 0.2s;
}
button:hover { background-color: #2980b9; }
button.secondary { background-color: #2ecc71; }
button.secondary:hover { background-color: #27ae60; }
button.warning { background-color: #e67e22; }
button.warning:hover { background-color: #d35400; }
#log-container {
background-color: #2c3e50;
color: #2ecc71;
padding: 15px;
border-radius: 8px;
font-family: "Courier New", Courier, monospace;
height: 300px;
overflow-y: auto;
white-space: pre-wrap;
box-shadow: inset 0 2px 4px rgba(0,0,0,0.3);
}
.status-bar {
display: inline-block;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: bold;
background-color: #bdc3c7;
color: white;
margin-bottom: 15px;
}
.loading { background-color: #f1c40f; color: #333; }
.idle { background-color: #95a5a6; }
</style>
</head>
<body>
<h1>Axios & Promise 综合实战大看板</h1>
<!– 状态指示器 –>
<div id="global-status" class="status-bar idle">当前状态: 空闲</div>
<!– 实验控制面板 –>
<div class="card">
<h3>控制台与操作面板</h3>
<p>请先启动 FastAPI 后端,然后点击下方按钮观察请求流。同时请**打开浏览器控制台 (F12)** 查看完整的网络对象数据。</p>
<div class="btn-group">
<button onclick="manageProductFlow()">1. 运行商品增删改链条 (实例方法)</button>
<button class="secondary" onclick="initDashboard()">2. 模拟大屏初始化 (并发方法)</button>
<button class="warning" onclick="loadBackupDataWithRace()">3. 触发数据竞速与降级 (高级方法)</button>
<button style="background-color: #95a5a6;" onclick="clearLogs()">清空日志</button>
</div>
</div>
<!– 实时可视日志 –>
<div class="card">
<h3>实时操作日志 (页面输出)</h3>
<div id="log-container">等待操作…</div>
</div>
<script>
// ==================== 1. AXIOS 全局配置 ====================
axios.defaults.baseURL = 'http://127.0.0.1:8000';
axios.defaults.timeout = 5000; // 全局超时 5 秒
// UI 日志辅助函数
const logBox = document.getElementById('log-container');
const statusBar = document.getElementById('global-status');
function printLog(message, type = 'info') {
const time = new Date().toLocaleTimeString();
let prefix = `[${time}] `;
if (type === 'error') prefix += '❌ ';
if (type === 'success') prefix += '✅ ';
if (logBox.innerText === '等待操作…') {
logBox.innerText = prefix + message + '\\n';
} else {
logBox.innerText += prefix + message + '\\n';
}
logBox.scrollTop = logBox.scrollHeight; // 滚动条自动滚到底
}
function setStatus(statusText, isLoading = false) {
statusBar.innerText = `当前状态: ${statusText}`;
if (isLoading) {
statusBar.className = "status-bar loading";
} else {
statusBar.className = "status-bar idle";
}
}
function clearLogs() {
logBox.innerText = '等待操作…';
}
// ==================== 2. 核心核心业务逻辑 ====================
// 场景 A:发布商品并连续修改、删除
// 涵盖:axios.post, axios.put, axios.delete, .then(), .catch(), .finally()
function manageProductFlow() {
printLog("=== 启动场景 1:商品流管理 ===");
setStatus("正在提交数据…", true);
// [Axios.post]: 新增一个商品 (接收 JSON 请求体)
axios.post('/items', { name: 'iPad Pro OLED', price: 7999.0 })
.then(response => {
// [Promise 实例方法 .then]
const newId = response.data.id;
printLog(`新增成功!FastAPI 返回新商品ID: ${newId}`, 'success');
console.log("新增响应详情:", response);
// 链式调用 [Axios.put]: 紧接着修改这个商品的价格
printLog(`继续链式调用:正在修改商品 ${newId} 的价格…`);
return axios.put(`/items/${newId}`, { name: 'iPad Pro OLED', price: 7499.0 });
})
.then(putResponse => {
printLog(`修改价格成功!最新价格: ${putResponse.data.data.price}`, 'success');
console.log("修改响应详情:", putResponse);
// 链式调用 [Axios.delete]: 删掉数据库里的商品 1
printLog("继续链式调用:正在尝试删除商品 ID: 1 …");
return axios.delete('/items/1');
})
.then(deleteResponse => {
printLog(`删除操作完成:${deleteResponse.data.message}`, 'success');
console.log("删除响应详情:", deleteResponse);
})
.catch(error => {
// [Promise 实例方法 .catch]: 统一捕获链条中任何一步发生的错误
printLog(`流程中某一步出错: ${error.message}`, 'error');
if (error.response) {
console.error("后端返回的错误状态码:", error.response.status);
}
})
.finally(() => {
// [Promise 实例方法 .finally]
setStatus("空闲");
printLog("场景 1 流程结束。");
});
}
// 场景 B:大屏数据并发初始化
// 涵盖:axios.get (Path参数), axios.get (Query参数), Promise.all, Promise.allSettled
async function initDashboard() {
printLog("=== 启动场景 2:大屏并发初始化 ===");
setStatus("正在并发请求多源数据…", true);
// 准备 3 个不同的 Axios 请求任务(注意:此时请求已发出,它们在并行执行)
const req1 = axios.get('/items/2'); // 成功 (Path参数)
const req2 = axios.get('/items', { params: { limit: 5 } }); // 成功 (Query参数)
const req3 = axios.get('/items/999'); // 必定失败:FastAPI 里没 999 这个商品,会报 404
// —- 演示 Promise.all (全胜才胜) —-
try {
printLog("执行 Promise.all 中(打包任务 1 和任务 2,预期全部成功)…");
const [res1, res2] = await Promise.all([req1, req2]);
printLog(`【Promise.all 成功】商品2名字: ${res1.data.name},列表商品数: ${res2.data.length}`, 'success');
} catch (err) {
printLog(`【Promise.all 失败】报错了: ${err.message}`, 'error');
}
// —- 演示 Promise.allSettled (全部等齐,不管输赢) —-
printLog("执行 Promise.allSettled 中(打包任务 1、2 以及必然失败的任务 3)…");
const results = await Promise.allSettled([req1, req2, req3]);
printLog("【Promise.allSettled 检查各通道结果】:");
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
printLog(` └─ 任务 ${index + 1} 成功,数据状态码: ${result.value.status}`, 'success');
} else {
// 任务 3 会走到这里
printLog(` └─ 任务 ${index + 1} 失败,原因: ${result.reason.message}`, 'error');
}
});
setStatus("空闲");
printLog("场景 2 流程结束。");
}
// 场景 C:高级竞速与本地降级
// 涵盖:Promise.race, Promise.any, Promise.resolve, Promise.reject
async function loadBackupDataWithRace() {
printLog("=== 启动场景 3:数据竞速与降级 ===");
setStatus("正在执行竞速逻辑…", true);
// 1. 创建一个自定义的前端 3 秒超时拒绝任务,用来和后端的慢接口“赛跑”
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error("前端自定义控制:服务器响应太慢,已被强行中断!")), 3000);
});
// —- 演示 Promise.race (谁快听谁) —-
try {
printLog("【Promise.race】让【4秒才响应的后端接口】与【3秒定时器】赛跑…");
// 后端慢接口需要 4 秒,定时器只需要 3 秒。定时器跑赢并触发了错误(reject),所以整体直接进 catch
await Promise.race([
axios.get('/slow-backup-source'),
timeoutPromise
]);
} catch (raceError) {
printLog(`【Promise.race 赛跑结果】: ${raceError.message}`, 'error');
}
// —- 演示 Promise.any (得一胜足矣) 与 静态快速创建 —-
printLog("【Promise.any】模拟多服务器并发容错(包含死服务器、好服务器、本地缓存)…");
const fastFailure = Promise.reject(new Error("一号镜像服务器发生致命闪退")); // [Promise.reject] 瞬间失败
const slowSuccess = axios.get('/items/2'); // 正常请求,需要几十毫秒
const localFallback = Promise.resolve({ data: { name: "本地离线降级商品", price: –1 } }); // [Promise.resolve] 瞬间成功
try {
// Promise.any 只要有一个成功就行。
// 此时 fastFailure 瞬间失败被忽略,slowSuccess 和 localFallback 都是成功的。
// 因为 localFallback 是本地同步代码瞬间完成,它最快,所以 any 最终会采纳 localFallback 的数据。
const winnerResponse = await Promise.any([fastFailure, slowSuccess, localFallback]);
printLog(`【Promise.any 胜出者数据】: ${winnerResponse.data.name}`, 'success');
console.log("Promise.any 拿到的完整对象: ", winnerResponse);
} catch (anyError) {
printLog("只有全部都失败了才会进到这里", 'error');
}
setStatus("空闲");
printLog("场景 3 流程结束。");
}
</script>
</body>
</html>
三,关于进阶的内容
除了请求方法,Axios 之所以能成为企业级开发的事实标准,是因为它围绕网络请求的生命周期、安全和工程化提供了非常完备的解决方案。
1.拦截器(Interceptors)
拦截器就像是请求和响应在路上的“安全检查站”。你可以在它们到达 FastAPI 或前端组件之前,统一对它们进行修改或拦截。
- 请求拦截器:在请求发出去之前执行。通常用来统一自动添加 Token。
- 响应拦截器:在数据回到 .then() 之前执行。通常用来统一剥离外壳数据或统一处理报错。
// 创建实例
const api = axios.create({ baseURL: 'http://127.0.0.1:8000' });
// 1. 请求拦截器
api.interceptors.request.use(config => {
const token = localStorage.getItem('user_token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
}, error => {
return Promise.reject(error);
});
// 2. 响应拦截器
api.interceptors.response.use(response => {
return response.data;
}, error => {
if (error.response && error.response.status === 401) {
alert("登录过期,请重新登录!");
window.location.href = '/login';
}
return Promise.reject(error);
});
2. 取消请求(CancelToken / AbortController)
在某些高频交互的场景下,前一个请求还没结束,用户又触发了新请求,为了节约带宽或防止数据错乱,需要取消上一次未完成的请求。
💡 现代推荐:使用浏览器原生的 AbortController(取代 Axios 自带的 CancelToken)。
典型场景:输入框搜索联想
let controller = null;
function searchProducts(keyword) {
if (controller) {
controller.abort();
}
controller = new AbortController();
axios.get('/search', {
params: { q: keyword },
signal: controller.signal
})
.then(res => console.log("搜索结果:", res.data))
.catch(err => {
if (axios.isCancel(err)) {
console.log("成功取消了上一次的高频重复请求");
} else {
console.error("真正的请求报错:", err);
}
});
}
3. 防范 XSS / CSRF 安全攻击
Axios 内置了对 CSRF(跨站请求伪造)的防御机制。如果后端在 Cookie 中写入了动态 Token(如 XSRF-TOKEN),Axios 会自动读取并添加到请求头 X-XSRF-TOKEN 中。
只需全局配置:
axios.defaults.xsrfCookieName = 'XSRF-TOKEN'; // 读取 Cookie 的名字
axios.defaults.xsrfHeaderName = 'X-XSRF-TOKEN'; // 写入 Header 的名字
4. 上传与下载的进度监听(Progress Events)
当上传大文件或下载超大 Excel 时,可监听进度以提升用户体验。
const formData = new FormData();
formData.append('file', fileObject);
axios.post('/upload', formData, {
onUploadProgress: (progressEvent) => {
const percent = Math.round((progressEvent.loaded * 100) / progressEvent.total);
console.log(`上传进度:${percent}%`);
// 更新进度条组件
}
})
.then(res => console.log("上传成功"));
5. 错误对象的层级划分(AxiosError)
Axios 抛出的错误包含丰富的网络上下文,便于定位问题。
axios.get('/data')
.catch(error => {
if (error.response) {
// 后端返回了非 2xx 状态码(如 404, 500, 422)
console.log("状态码:", error.response.status);
console.log("报错详情:", error.response.data);
} else if (error.request) {
// 请求发出但未收到响应(超时、断网、服务未启动)
console.log("无响应:", error.request);
} else {
// 请求配置阶段出错(极少见)
console.log("请求错误:", error.message);
}
});


