关于前后端交互的数据的方式总结对比传统ajax
在 FastAPI 网站的前后端交互中,以音乐网站应用为例,主要使用了以下几种交互方法,它们都是 异步 的(因为 FastAPI 是异步框架,现代前端也主要使用异步交互): ## 1. 获取数据 (GET 请求)
• 用途 :获取音乐列表、获取单首音乐信息 • 同步/异步 :异步(前端 fetch + 后端 async ) • 示例 : ```
// 前端异步获取音乐列表
async function loadMusicList() {
const response = await fetch(${API_BASE_URL}/music/);
musicList = await response.json();
}
关于前后端交互的数据的方式总结对比传统ajax
@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}`
});
}
# 关于前后端交互的数据的方式总结对比传统ajax
@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
});
}
关于前后端交互的数据的方式总结对比传统ajax
@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}`;
# 关于前后端交互的数据的方式总结对比传统ajax
@app.get("/stream/{music_id}")
async def stream_music(music_id: str):
return FileResponse(music.file_path)
5. WebSocket(可选扩展)
• 用途 :实时聊天、播放同步 • 同步/异步 :异步(双向通信) • 示例 : ```
关于前后端交互的数据的方式总结对比传统ajax
@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/");
2. 添加加载状态 (提升用户体验) ```
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 部分 (保持不变)
音乐网站 (Axios 版本)
/* 之前的样式代码 */
音乐网站 (Axios 版本)
<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>
```
## 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 改进点说明
1. 自动 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 => { // 统一错误处理 });
3. 更简洁的 API : • 直接使用 `axios.get()` , `axios.post()` 等方法 • 自动处理 Content-Type 头(根据数据类型)
4. 更好的错误处理 : • 区分网络错误、请求错误和响应错误 • 示例: ```
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('操作被用户取消');
6. 上传进度 : • 支持监听上传进度 • 示例: ```
axios.post(url, data, {
onUploadProgress: progressEvent => {
const percent = Math.round((progressEvent.loaded * 100) / progressEvent.total);
console.log(`上传进度: ${percent}%`);
}
});
总结
使用 Axios 改写的音乐网站前端具有以下优势: 1. 代码更简洁易读
2. 自动处理 JSON 数据
3. 统一的错误处理机制
4. 支持请求/响应拦截
5. 更好的浏览器兼容性(包括 IE11)
6. 提供更多高级功能(取消请求、进度监控等)
这种实现方式更适合生产环境,特别是需要处理复杂请求和错误场景的应用。 ## 和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}%); };
3. 需要精确控制超时 ```
xhr.timeout = 5000; // 5秒超时
xhr.ontimeout = () => console.log("请求超时");
6. 总结
技术 推荐场景 不推荐场景 Fetch API 现代浏览器、简单请求 需要进度监控、兼容 IE Axios 需要拦截器、自动 JSON 转换、兼容 IE 对包体积敏感( axios 需要额外引入) AJAX 需要上传进度、超时控制、兼容旧代码 新项目开发 建议: • 新项目优先用 fetch 或 axios (后者功能更全)。 • 旧项目或特殊需求(如上传进度)才用 XMLHttpRequest 。