你有没有遇到过这种情况?
👉 疯狂点击分页按钮 ,页面数据却像抽风一样,一会儿显示第3页,一会儿又跳回第2页?
👉 快速滑动无限滚动列表,结果数据错乱,甚至重复加载?

这就是前端分页的竞态问题在作怪!
今天,我们就用搞笑+实战的方式,彻底解决它!
🔥 1. 什么是分页竞态问题?
想象一下:
- 你疯狂点击 "下一页" 按钮,连发5次请求:
- 第1次请求(第2页) → 网络慢,还没回来
- 第2次请求(第3页) → 先回来了!
- 第1次请求(第2页)终于回来了 → 覆盖了第3页的数据!
结果:你明明想看第5页,却看到第2页的数据! 😵
这就是 "竞态问题" (Race Condition)------请求赛跑,谁慢谁尴尬!
🔥 2. 5种方法解决分页竞态问题
方法1:直接干掉慢的请求(AbortController)
"谁慢,就取消谁!"
js
let controller = null; // 记录当前请求
async function fetchPage(pageNum) {
// 如果上次请求还没完成,直接取消!
if (controller) controller.abort();
controller = new AbortController(); // 新建一个控制器
try {
const response = await fetch(`/api/data?page=${pageNum}`, {
signal: controller.signal // 绑定取消信号
});
const data = await response.json();
renderData(data); // 渲染数据
} catch (err) {
if (err.name !== 'AbortError') {
console.error("请求出错:", err);
}
}
}
适用场景 :现代浏览器(IE再见👋),精准控制请求
方法2:只认最后一个请求(Request ID)
"不管谁先回来,我只认最后一个!"
javascript
let lastRequestId = 0; // 记录最新请求ID
async function fetchPage(pageNum) {
const currentRequestId = ++lastRequestId; // 生成新ID
const response = await fetch(`/api/data?page=${pageNum}`);
const data = await response.json();
// 如果这个请求是最新的,才渲染!
if (currentRequestId === lastRequestId) {
renderData(data);
}
}
适用场景 :简单暴力,适用于所有框架
方法3:防抖(Debounce)
"别点太快,等我喘口气!"
javascript
import { debounce } from 'lodash';
// 300ms内只执行最后一次
const fetchPage = debounce(async (pageNum) => {
const response = await fetch(`/api/data?page=${pageNum}`);
const data = await response.json();
renderData(data);
}, 300);
适用场景 :减少无效请求,适合搜索框+分页结合
方法4:乐观更新(Optimistic UI)
"先假装成功,失败了再撤回!"
javascript
async function fetchPage(pageNum) {
// 先更新UI(假设请求会成功)
updatePaginationUI(pageNum);
try {
const response = await fetch(`/api/data?page=${pageNum}`);
const data = await response.json();
renderData(data);
} catch (err) {
console.error("请求失败:", err);
// 回滚UI
revertPaginationUI();
}
}
适用场景 :社交APP(如微博、Twitter),提升用户体验
方法5:后端配合(请求序号)
"让后端告诉我,这是不是最新的数据!"
javascript
let lastValidPage = 1;
async function fetchPage(pageNum) {
const response = await fetch(`/api/data?page=${pageNum}`);
const { data, currentPage } = await response.json();
// 只更新最新的数据
if (currentPage >= lastValidPage) {
lastValidPage = currentPage;
renderData(data);
}
}
适用场景 :需要前后端配合,精准控制数据
🔥 3. 最佳实践推荐
✅ 优先用 AbortController
(现代浏览器支持,精准取消请求)
✅ 结合防抖 (减少无效请求)
✅ 乐观更新(提升用户体验,适合社交媒体)
🔥 4. 扩展场景
无限滚动(Infinite Scroll)
用 Intersection Observer 监听滚动,避免重复加载:
javascript
const observer = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) {
loadMoreData(); // 加载更多
}
});
observer.observe(document.querySelector("#load-more-trigger"));
React/Vue 组件卸载时取消请求
javascript
// React示例
useEffect(() => {
const controller = new AbortController();
fetchData({ signal: controller.signal });
return () => controller.abort(); // 组件卸载时取消请求
}, []);
🔥 5. 总结
方法 | 适用场景 | 优点 |
---|---|---|
AbortController | 现代浏览器 | 精准取消请求 |
Request ID | 所有框架 | 简单可靠 |
Debounce | 搜索+分页 | 减少无效请求 |
乐观更新 | 社交媒体 | 提升体验 |
后端序号 | 需要配合 | 数据精准 |
现在,你可以放心狂点分页按钮了!🚀
你的项目用的是哪种分页方式?欢迎留言讨论! 😆