Skip to content

对于分页的列表,怎么解决快速翻页场景下的竞态问题?

在分页列表中,快速翻页可能导致竞态问题(race condition),即较早发起的请求返回数据覆盖了后发起请求的数据,导致页面显示错误的内容。

竞态问题的产生场景

  1. 用户快速翻页,比如从第 1 页直接跳到第 10 页。
  2. 第 1 页的请求先发起,但第 10 页的请求后发起。
  3. 如果第 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) 减少无效请求。
  • 如果需要更精细的控制,可以结合后端能力实现优先级处理。

通过这些方案,可以确保分页列表在快速翻页场景下始终显示用户期望的数据。