对于分页的列表,怎么解决快速翻页场景下的竞态问题?
在分页列表中,快速翻页可能导致竞态问题(race condition),即较早发起的请求返回数据覆盖了后发起请求的数据,导致页面显示错误的内容。
竞态问题的产生场景
- 用户快速翻页,比如从第 1 页直接跳到第 10 页。
- 第 1 页的请求先发起,但第 10 页的请求后发起。
- 如果第 1 页的请求响应比第 10 页的请求响应更快,页面可能会显示第 1 页的数据,而非用户期望的第 10 页。
解决竞态问题的方案
1. 通过唯一标识取消过时的请求
使用请求的唯一标识(如页码)来取消过时的请求,避免页面加载不需要的数据。
实现方法
借助 axios 提供的 CancelToken 或 Fetch 的 AbortController:
javascript
let cancelToken;
function fetchPage(pageNumber) {
if (cancelToken) {
cancelToken.cancel("Request canceled due to new request.");
}
cancelToken = axios.CancelToken.source();
return axios.get(`/api/data?page=${pageNumber}`, {
cancelToken: cancelToken.token,
});
}2. 仅渲染最后一次请求的数据
通过记录当前的有效请求标识,仅允许最后一次请求返回的数据更新页面。
实现方法
在发送请求时生成标识,并在请求返回时比对:
javascript
let currentRequestId = 0;
function fetchPage(pageNumber) {
const requestId = ++currentRequestId;
return axios.get(`/api/data?page=${pageNumber}`).then((response) => {
if (requestId === currentRequestId) {
// 只处理最后一次请求的响应
renderData(response.data);
}
});
}3. 缓存请求结果
对于快速翻页场景,可能存在页码反复访问的情况。通过缓存请求结果,可以直接使用已有数据,而不是重复发送请求。
实现方法
使用一个 Map 存储页码和对应数据:
javascript
const cache = new Map();
async function fetchPage(pageNumber) {
if (cache.has(pageNumber)) {
return renderData(cache.get(pageNumber));
}
const response = await axios.get(`/api/data?page=${pageNumber}`);
cache.set(pageNumber, response.data);
renderData(response.data);
}4. 限制请求频率
使用防抖或节流机制,减少过于频繁的请求发起。
实现方法
使用防抖函数控制请求触发:
javascript
function debounce(fn, delay) {
let timer = null;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
const fetchPageDebounced = debounce(fetchPage, 300);5. 后端支持分页请求的取消或优先级控制
如果后端能够识别请求的优先级,可以通过返回特定状态码(如 409 Conflict)告知前端忽略掉过时的请求。
题目要点:
解决分页列表快速翻页场景下的竞态问题,需要根据项目需求选择合适的方案:
- 如果需要实时性强且无需保存历史请求结果,优先使用唯一标识控制(方案 2)。
- 如果页面频繁访问同一页,优先使用请求缓存(方案 3)。
- 如果用户交互频繁,建议结合 防抖机制(方案 4) 减少无效请求。
- 如果需要更精细的控制,可以结合后端能力实现优先级处理。
通过这些方案,可以确保分页列表在快速翻页场景下始终显示用户期望的数据。