关于前后端交互的数据的方式(总结对比传统ajax)
在 FastAPI 网站的前后端交互中,以音乐网站应用为例,主要使用了以下几种交互方法,它们都是异步的(因为 FastAPI 是异步框架,现代前端也主要使用异步交互):
1. 获取数据 (GET 请求)
• 用途:获取音乐列表、获取单首音乐信息
• 同步/异步:异步(前端 fetch + 后端 async)
• 示例:
// 前端异步获取音乐列表
async function loadMusicList() {
const response = await fetch(`${API_BASE_URL}/music/`);
musicList = await response.json();
}
# 后端异步返回数据
@app.get("/music/", response_model=List[Music])
async def get_music_list():
return fake_music_db
2. 提交数据 (POST 请求)
• 用途:用户登录、上传音乐
• 同步/异步:异步(前端 fetch + 后端 async)
• 示例:
// 前端异步提交登录表单
async function login() {
const response = await fetch(`${API_BASE_URL}/token`, {
method: "POST",
body: `username=${username}&password=${password}`
});
}
# 后端异步处理登录
@app.post("/token", response_model=Token)
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
user = authenticate_user(fake_users_db, form_data.username, form_data.password)
return {"access_token": access_token, "token_type": "bearer"}
3. 文件上传 (Multipart Form Data)
• 用途:上传音乐文件
• 同步/异步:异步(前端 FormData + 后端 UploadFile)
• 示例:
// 前端异步上传文件
async function uploadMusic() {
const formData = new FormData();
formData.append("file", file);
const response = await fetch(`${API_BASE_URL}/upload/`, {
method: "POST",
body: formData
});
}
# 后端异步接收文件
@app.post("/upload/")
async def upload_music(
file: UploadFile = File(...),
current_user: User = Depends(get_current_active_user)
):
with open(file_path, "wb") as buffer:
await buffer.write(await file.read())
4. 流媒体传输 (Streaming)
• 用途:播放音乐(支持 Range 请求)
• 同步/异步:异步(前端 <audio> + 后端文件流)
• 示例:
// 前端用 <audio> 播放
audioPlayer.src = `${API_BASE_URL}/stream/${music.id}`;
# 后端返回文件流(FastAPI 会自动处理 Range 请求)
@app.get("/stream/{music_id}")
async def stream_music(music_id: str):
return FileResponse(music.file_path)
5. WebSocket(可选扩展)
• 用途:实时聊天、播放同步
• 同步/异步:异步(双向通信)
• 示例:
# 后端 WebSocket
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
while True:
data = await websocket.receive_text()
await websocket.send_text(f"Message: {data}")
// 前端 WebSocket
const socket = new WebSocket("ws://localhost:8000/ws");
socket.onmessage = (event) => {
console.log(event.data);
};
完整前端代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>音乐网站</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
background-color: #f5f5f5;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
header {
background-color: #333;
color: white;
padding: 10px 0;
margin-bottom: 20px;
}
header h1 {
margin: 0;
padding: 0 20px;
}
.auth-section {
margin-bottom: 20px;
padding: 20px;
background-color: white;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
.upload-section {
margin-bottom: 20px;
padding: 20px;
background-color: white;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
.music-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 20px;
}
.music-card {
background-color: white;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
padding: 15px;
transition: transform 0.2s;
}
.music-card:hover {
transform: translateY(-5px);
}
.music-card h3 {
margin-top: 0;
}
.player {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background-color: #333;
color: white;
padding: 10px;
display: flex;
align-items: center;
}
.player audio {
flex-grow: 1;
margin: 0 20px;
}
button {
background-color: #4CAF50;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #45a049;
}
input, textarea {
width: 100%;
padding: 8px;
margin: 8px 0;
box-sizing: border-box;
border: 1px solid #ccc;
border-radius: 4px;
}
</style>
</head>
<body>
<header>
<div class="container">
<h1>音乐网站</h1>
</div>
</header>
<div class="container">
<div class="auth-section" id="authSection">
<h2>登录</h2>
<div id="loginForm">
<input type="text" id="username" placeholder="用户名" required>
<input type="password" id="password" placeholder="密码" required>
<button onclick="login()">登录</button>
<p id="authMessage"></p>
</div>
<div id="userInfo" style="display: none;">
<p>欢迎, <span id="displayUsername"></span></p>
<button onclick="logout()">登出</button>
</div>
</div>
<div class="upload-section" id="uploadSection" style="display: none;">
<h2>上传音乐</h2>
<form id="uploadForm">
<input type="text" id="musicTitle" placeholder="歌曲标题" required>
<input type="text" id="musicArtist" placeholder="艺术家" required>
<input type="number" id="musicDuration" placeholder="时长(秒)" required>
<input type="file" id="musicFile" accept="audio/*" required>
<button type="button" onclick="uploadMusic()">上传</button>
<p id="uploadMessage"></p>
</form>
</div>
<h2>音乐列表</h2>
<div class="music-list" id="musicList">
<!-- 音乐列表将通过JavaScript动态加载 -->
</div>
</div>
<div class="player" id="player" style="display: none;">
<button onclick="previousSong()">上一首</button>
<audio id="audioPlayer" controls></audio>
<button onclick="nextSong()">下一首</button>
<div id="nowPlaying"></div>
</div>
<script>
let accessToken = null;
let currentUser = null;
let musicList = [];
let currentPlayingIndex = -1;
const API_BASE_URL = 'http://localhost:8000';
// 登录函数
async function login() {
const username = document.getElementById('username').value;
const password = document.getElementById('password').value;
try {
const response = await fetch(`${API_BASE_URL}/token`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: `username=${encodeURIComponent(username)}&password=${encodeURIComponent(password)}`
});
if (response.ok) {
const data = await response.json();
accessToken = data.access_token;
currentUser = username;
// 更新UI
document.getElementById('loginForm').style.display = 'none';
document.getElementById('userInfo').style.display = 'block';
document.getElementById('displayUsername').textContent = username;
document.getElementById('uploadSection').style.display = 'block';
document.getElementById('authMessage').textContent = '';
// 加载音乐列表
loadMusicList();
} else {
document.getElementById('authMessage').textContent = '登录失败,请检查用户名和密码';
}
} catch (error) {
console.error('登录错误:', error);
document.getElementById('authMessage').textContent = '登录时发生错误';
}
}
// 登出函数
function logout() {
accessToken = null;
currentUser = null;
// 更新UI
document.getElementById('loginForm').style.display = 'block';
document.getElementById('userInfo').style.display = 'none';
document.getElementById('uploadSection').style.display = 'none';
document.getElementById('player').style.display = 'none';
document.getElementById('authMessage').textContent = '';
}
// 上传音乐
async function uploadMusic() {
if (!accessToken) {
alert('请先登录');
return;
}
const title = document.getElementById('musicTitle').value;
const artist = document.getElementById('musicArtist').value;
const duration = document.getElementById('musicDuration').value;
const fileInput = document.getElementById('musicFile');
const file = fileInput.files[0];
if (!title || !artist || !duration || !file) {
document.getElementById('uploadMessage').textContent = '请填写所有字段';
return;
}
const formData = new FormData();
formData.append('title', title);
formData.append('artist', artist);
formData.append('duration', duration);
formData.append('file', file);
try {
const response = await fetch(`${API_BASE_URL}/upload/`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`
},
body: formData
});
if (response.ok) {
const data = await response.json();
document.getElementById('uploadMessage').textContent = '上传成功!';
document.getElementById('uploadForm').reset();
// 重新加载音乐列表
loadMusicList();
} else {
document.getElementById('uploadMessage').textContent = '上传失败';
}
} catch (error) {
console.error('上传错误:', error);
document.getElementById('uploadMessage').textContent = '上传时发生错误';
}
}
// 加载音乐列表
async function loadMusicList() {
try {
const response = await fetch(`${API_BASE_URL}/music/`);
if (response.ok) {
musicList = await response.json();
renderMusicList();
}
} catch (error) {
console.error('加载音乐列表错误:', error);
}
}
// 渲染音乐列表
function renderMusicList() {
const musicListContainer = document.getElementById('musicList');
musicListContainer.innerHTML = '';
musicList.forEach((music, index) => {
const musicCard = document.createElement('div');
musicCard.className = 'music-card';
musicCard.innerHTML = `
<h3>${music.title}</h3>
<p>艺术家: ${music.artist}</p>
<p>时长: ${Math.floor(music.duration / 60)}:${(music.duration % 60).toString().padStart(2, '0')}</p>
<p>上传者: ${music.uploader}</p>
<button onclick="playMusic(${index})">播放</button>
`;
musicListContainer.appendChild(musicCard);
});
}
// 播放音乐
function playMusic(index) {
if (index < 0 || index >= musicList.length) return;
currentPlayingIndex = index;
const music = musicList[index];
document.getElementById('player').style.display = 'flex';
document.getElementById('nowPlaying').innerHTML = `
正在播放: ${music.title} - ${music.artist}
`;
// 设置音频源
const audioPlayer = document.getElementById('audioPlayer');
audioPlayer.src = `${API_BASE_URL}/stream/${music.id}`;
audioPlayer.play();
}
// 上一首
function previousSong() {
if (currentPlayingIndex <= 0) return;
playMusic(currentPlayingIndex - 1);
}
// 下一首
function nextSong() {
if (currentPlayingIndex >= musicList.length - 1) return;
playMusic(currentPlayingIndex + 1);
}
// 初始化
document.addEventListener('DOMContentLoaded', () => {
loadMusicList();
});
</script>
</body>
</html>
总结:前后端交互方式
| 交互方式 | HTTP 方法 | 同步/异步 | 用途 |
|---|---|---|---|
| 获取数据 (GET) | GET | 异步 | 加载音乐列表、用户信息 |
| 提交数据 (POST) | POST | 异步 | 登录、表单提交 |
| 文件上传 (FormData) | POST | 异步 | 上传音乐文件 |
| 流媒体 (Streaming) | GET | 异步 | 播放音乐(支持断点续传) |
| WebSocket | - | 异步 | 实时通信(聊天、播放同步) |
为什么都是异步?
- 前端:现代前端使用
fetch或axios,都是基于Promise的异步请求。 - 后端:FastAPI 是异步框架,使用
async/await处理请求,适合高并发场景(如音乐流媒体)。 - 用户体验:异步交互不会阻塞页面,用户可以继续操作(如浏览其他音乐)。
扩展建议
- 使用
axios替代fetch(更友好、支持拦截器)import axios from "axios"; const response = await axios.get("/music/"); - 添加加载状态(提升用户体验)
async function loadMusicList() { setLoading(true); try { const response = await fetch(...); // ... } finally { setLoading(false); } } - 优化流媒体传输(使用
Range请求)@app.get("/stream/{music_id}") async def stream_music(music_id: str, range: str = Header(None)): # 处理 Range 请求(支持断点续传) return FileResponse(..., headers={"Accept-Ranges": "bytes"})
这样你的音乐网站就能高效、流畅地运行!🎵
使用 Axios 改写的音乐网站前端
下面是将之前使用 fetch 的音乐网站前端代码改用 axios 实现的完整版本。Axios 提供了更简洁的 API、自动 JSON 转换、请求/响应拦截器等优势。
HTML 部分 (保持不变)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>音乐网站 (Axios 版本)</title>
<!-- 样式部分保持不变 -->
<style>
/* 之前的样式代码 */
</style>
</head>
<body>
<!-- HTML 结构保持不变 -->
<header>
<div class="container">
<h1>音乐网站 (Axios 版本)</h1>
</div>
</header>
<div class="container">
<div class="auth-section" id="authSection">
<!-- 登录表单保持不变 -->
</div>
<div class="upload-section" id="uploadSection" style="display: none;">
<!-- 上传表单保持不变 -->
</div>
<h2>音乐列表</h2>
<div class="music-list" id="musicList">
<!-- 音乐列表将通过JavaScript动态加载 -->
</div>
</div>
<div class="player" id="player" style="display: none;">
<!-- 播放器控件保持不变 -->
</div>
<!-- 引入 Axios -->
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
// JavaScript 部分将用 Axios 重写
</script>
</body>
</html>
JavaScript 部分 (Axios 实现)
// 配置
let accessToken = null;
let currentUser = null;
let musicList = [];
let currentPlayingIndex = -1;
const API_BASE_URL = 'http://localhost:8000';
// 创建 Axios 实例
const api = axios.create({
baseURL: API_BASE_URL,
timeout: 5000,
});
// 请求拦截器 (用于添加 JWT Token)
api.interceptors.request.use(config => {
if (accessToken) {
config.headers.Authorization = `Bearer ${accessToken}`;
}
return config;
}, error => {
return Promise.reject(error);
});
// 响应拦截器 (统一处理错误)
api.interceptors.response.use(response => {
return response;
}, error => {
if (error.response) {
switch (error.response.status) {
case 401:
alert('认证失败,请重新登录');
logout();
break;
case 403:
alert('没有权限执行此操作');
break;
case 404:
alert('请求的资源不存在');
break;
default:
alert(`请求错误: ${error.response.status}`);
}
} else if (error.request) {
alert('网络错误,请检查网络连接');
} else {
alert('请求配置错误');
}
return Promise.reject(error);
});
// 登录函数
async function login() {
const username = document.getElementById('username').value;
const password = document.getElementById('password').value;
try {
// 使用 URLSearchParams 处理表单数据
const params = new URLSearchParams();
params.append('username', username);
params.append('password', password);
const response = await api.post('/token', params, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
});
accessToken = response.data.access_token;
currentUser = username;
// 更新UI
document.getElementById('loginForm').style.display = 'none';
document.getElementById('userInfo').style.display = 'block';
document.getElementById('displayUsername').textContent = username;
document.getElementById('uploadSection').style.display = 'block';
document.getElementById('authMessage').textContent = '';
// 加载音乐列表
loadMusicList();
} catch (error) {
document.getElementById('authMessage').textContent = '登录失败,请检查用户名和密码';
}
}
// 登出函数
function logout() {
accessToken = null;
currentUser = null;
// 更新UI
document.getElementById('loginForm').style.display = 'block';
document.getElementById('userInfo').style.display = 'none';
document.getElementById('uploadSection').style.display = 'none';
document.getElementById('player').style.display = 'none';
document.getElementById('authMessage').textContent = '';
}
// 上传音乐
async function uploadMusic() {
if (!accessToken) {
alert('请先登录');
return;
}
const title = document.getElementById('musicTitle').value;
const artist = document.getElementById('musicArtist').value;
const duration = document.getElementById('musicDuration').value;
const fileInput = document.getElementById('musicFile');
const file = fileInput.files[0];
if (!title || !artist || !duration || !file) {
document.getElementById('uploadMessage').textContent = '请填写所有字段';
return;
}
const formData = new FormData();
formData.append('title', title);
formData.append('artist', artist);
formData.append('duration', duration);
formData.append('file', file);
try {
const response = await api.post('/upload/', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
});
document.getElementById('uploadMessage').textContent = '上传成功!';
document.getElementById('uploadForm').reset();
// 重新加载音乐列表
loadMusicList();
} catch (error) {
document.getElementById('uploadMessage').textContent = '上传失败';
}
}
// 加载音乐列表
async function loadMusicList() {
try {
const response = await api.get('/music/');
musicList = response.data;
renderMusicList();
} catch (error) {
console.error('加载音乐列表错误:', error);
}
}
// 渲染音乐列表
function renderMusicList() {
const musicListContainer = document.getElementById('musicList');
musicListContainer.innerHTML = '';
musicList.forEach((music, index) => {
const musicCard = document.createElement('div');
musicCard.className = 'music-card';
musicCard.innerHTML = `
<h3>${music.title}</h3>
<p>艺术家: ${music.artist}</p>
<p>时长: ${Math.floor(music.duration / 60)}:${(music.duration % 60).toString().padStart(2, '0')}</p>
<p>上传者: ${music.uploader}</p>
<button onclick="playMusic(${index})">播放</button>
`;
musicListContainer.appendChild(musicCard);
});
}
// 播放音乐
function playMusic(index) {
if (index < 0 || index >= musicList.length) return;
currentPlayingIndex = index;
const music = musicList[index];
document.getElementById('player').style.display = 'flex';
document.getElementById('nowPlaying').innerHTML = `
正在播放: ${music.title} - ${music.artist}
`;
// 设置音频源
const audioPlayer = document.getElementById('audioPlayer');
audioPlayer.src = `${API_BASE_URL}/stream/${music.id}`;
audioPlayer.play();
}
// 上一首
function previousSong() {
if (currentPlayingIndex <= 0) return;
playMusic(currentPlayingIndex - 1);
}
// 下一首
function nextSong() {
if (currentPlayingIndex >= musicList.length - 1) return;
playMusic(currentPlayingIndex + 1);
}
// 初始化
document.addEventListener('DOMContentLoaded', () => {
loadMusicList();
});
Axios 改进点说明
-
自动 JSON 处理:
• Axios 自动解析 JSON 响应,无需手动调用response.json()
• 示例对比:// fetch const response = await fetch(url); const data = await response.json(); // axios const response = await axios.get(url); const data = response.data; // 自动解析 -
请求/响应拦截器:
• 统一添加 JWT Token 到请求头
• 统一处理错误响应
• 示例:// 请求拦截器 api.interceptors.request.use(config => { if (accessToken) { config.headers.Authorization = `Bearer ${accessToken}`; } return config; }); // 响应拦截器 api.interceptors.response.use(response => { return response; }, error => { // 统一错误处理 }); -
更简洁的 API:
• 直接使用axios.get(),axios.post()等方法
• 自动处理 Content-Type 头(根据数据类型) -
更好的错误处理:
• 区分网络错误、请求错误和响应错误
• 示例:try { await axios.get(url); } catch (error) { if (error.response) { // 请求已发出,服务器返回状态码不在 2xx 范围 } else if (error.request) { // 请求已发出但没有收到响应 } else { // 设置请求时出错 } } -
取消请求:
• 支持使用 CancelToken 取消请求
• 示例:const source = axios.CancelToken.source(); axios.get(url, { cancelToken: source.token }); // 取消请求 source.cancel('操作被用户取消'); -
上传进度:
• 支持监听上传进度
• 示例:axios.post(url, data, { onUploadProgress: progressEvent => { const percent = Math.round((progressEvent.loaded * 100) / progressEvent.total); console.log(`上传进度: ${percent}%`); } });
总结
使用 Axios 改写的音乐网站前端具有以下优势:
- 代码更简洁易读
- 自动处理 JSON 数据
- 统一的错误处理机制
- 支持请求/响应拦截
- 更好的浏览器兼容性(包括 IE11)
- 提供更多高级功能(取消请求、进度监控等)
这种实现方式更适合生产环境,特别是需要处理复杂请求和错误场景的应用。
和ajax的区别
在 FastAPI 前后端交互中,我们主要使用 fetch API 或 axios 进行异步请求,而传统的 AJAX(基于 XMLHttpRequest) 也是一种前后端交互方式。以下是它们的核心区别:
1. 基本概念
| 特性 | Fetch API | AJAX (XMLHttpRequest) |
|---|---|---|
| 诞生时间 | ES6(2015)引入,较新 | 1999年由微软提出,较老 |
| 语法 | 基于 Promise,更简洁 |
基于回调函数,代码冗长 |
| 数据格式 | 默认支持 JSON、FormData、Blob |
需要手动解析 JSON |
| 错误处理 | 需检查 response.ok |
通过 onerror 回调处理 |
| 兼容性 | 现代浏览器(IE 不兼容) | 所有浏览器(包括旧版 IE) |
2. 代码对比
(1) 发起 GET 请求
Fetch API
async function getMusicList() {
try {
const response = await fetch("/music/");
if (!response.ok) throw new Error("请求失败");
const data = await response.json();
console.log(data);
} catch (error) {
console.error("Error:", error);
}
}
AJAX (XMLHttpRequest)
function getMusicList() {
const xhr = new XMLHttpRequest();
xhr.open("GET", "/music/");
xhr.onload = function() {
if (xhr.status === 200) {
const data = JSON.parse(xhr.responseText);
console.log(data);
} else {
console.error("请求失败");
}
};
xhr.onerror = function() {
console.error("网络错误");
};
xhr.send();
}
(2) 发起 POST 请求(提交 JSON)
Fetch API
async function login(username, password) {
const response = await fetch("/token", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username, password }),
});
const data = await response.json();
}
AJAX (XMLHttpRequest)
function login(username, password) {
const xhr = new XMLHttpRequest();
xhr.open("POST", "/token");
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onload = function() {
const data = JSON.parse(xhr.responseText);
};
xhr.send(JSON.stringify({ username, password }));
}
3. 核心区别
| 对比项 | Fetch API | AJAX (XMLHttpRequest) |
|---|---|---|
| 语法简洁性 | ✅ 更简洁(async/await) |
❌ 回调嵌套,代码冗长 |
| Promise 支持 | ✅ 原生基于 Promise |
❌ 需手动封装成 Promise |
| Streaming 支持 | ✅ 支持 response.body (流式读取) |
❌ 不支持 |
| 请求取消 | ✅ 通过 AbortController |
✅ 通过 xhr.abort() |
| 超时控制 | ❌ 需手动封装 | ✅ 原生支持 xhr.timeout |
| 进度监控 | ❌ 不支持 | ✅ 通过 xhr.upload.onprogress |
| 兼容性 | ❌ 不兼容 IE | ✅ 兼容所有浏览器 |
4. 为什么现代项目更推荐 fetch 或 axios?
-
更简洁的语法
•fetch使用Promise,配合async/await代码更清晰。
• AJAX 需要手动处理回调,容易陷入“回调地狱”。 -
更好的数据格式支持
•fetch直接支持JSON、FormData、Blob等格式。
• AJAX 需要手动解析JSON或设置xhr.responseType。 -
更现代的扩展功能
•fetch支持Streaming(流式读取大数据)。
•axios提供拦截器、自动转换 JSON、请求取消等功能。 -
未来趋势
• 新浏览器已全面支持fetch,而XMLHttpRequest是旧技术。
5. 什么情况下仍需要用 AJAX?
-
需要兼容旧版浏览器(如 IE11)
•fetch不兼容 IE,但axios内部会回退到XMLHttpRequest。 -
需要上传进度监控
xhr.upload.onprogress = (event) => { const percent = Math.round((event.loaded / event.total) * 100); console.log(`上传进度: ${percent}%`); }; -
需要精确控制超时
xhr.timeout = 5000; // 5秒超时 xhr.ontimeout = () => console.log("请求超时");
6. 总结
| 技术 | 推荐场景 | 不推荐场景 |
|---|---|---|
| Fetch API | 现代浏览器、简单请求 | 需要进度监控、兼容 IE |
| Axios | 需要拦截器、自动 JSON 转换、兼容 IE | 对包体积敏感(axios 需要额外引入) |
| AJAX | 需要上传进度、超时控制、兼容旧代码 | 新项目开发 |
建议:
• 新项目优先用 fetch 或 axios(后者功能更全)。
• 旧项目或特殊需求(如上传进度)才用 XMLHttpRequest。

浙公网安备 33010602011771号