【JavaScript】通过AJAX技术让前端发请求到后端
目录
一,AJAX是什么
AJAX 是 Asynchronous JavaScript and XML(异步 JavaScript 和 XML)的缩写。
它的核心作用是:让网页在不重新加载整个页面的情况下,向服务器发送请求并局部更新页面。
比如在百度的搜索框中,我们输一个字下方就会立马弹出一串联想词,但是这并不是直接刷新整个页面,而是AJAX在后台随着输入,不断向服务器请求匹配,局部刷新下面那一串联想词。
AJAX实际是以下几种技术的组合:
- HTML / CSS: 负责网页的结构和样式(长成什么样)。
- DOM(文档对象模型): 负责动态显示和与数据交互(怎么局部改变网页内容)。
- Fetch API 或 XMLHttpRequest 对象: (最核心) 浏览器自带的幕后英雄,负责在后台悄悄与服务器交换数据。
- 数据格式(JSON / XML): 服务器返回的数据格式。虽然 AJAX 名字里有 XML,但因为 XML 太繁琐,现在行业内 99% 都在使用更轻量、更好读的 JSON 格式。
工作流程:
[ 网页触发事件 ] (如点击按钮)
│
▼
[ 创建请求并发送 ] (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 方法 | 对应的 HTTP 动作 | 典型应用场景 | 第二个参数填什么? | 第三个参数填什么? | 对应的 FastAPI 接收注解 |
|---|---|---|---|---|---|
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);
}
});
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)