前端请求竞态

3 阅读1分钟

前言

前端里常见的问题请求竞态。 尤其是搜索、筛选、联动查询这类高频场景,用户操作很快,请求也会连续发出。 但接口返回顺序不一定和发出顺序一致,最后就很容易出现:

新请求发出去了,旧请求却最后返回 页面被旧数据覆盖 UI 状态和用户操作对不上

问题复现

比如搜索框里做联想查询:

  1. 用户输入 a,发起一次请求
  2. 很快又输入 ab,再发一次请求
  3. 接着输入 abc,继续发请求

正常情况下,最后页面应该展示 abc 对应的数据。 但如果 a 这个请求返回得更晚,就可能把 abc 的结果覆盖掉。

结果就是:

用户明明输入的是 abc 页面却显示 a 的结果 体验非常差 这个问题的根源,就是异步请求返回顺序不可控。

实现思路 思路其实很简单:

每次调用时,给请求一个递增序号 记录当前最新的序号 请求返回时,检查自己是不是最新那次 如果不是,直接丢弃结果 这样就能保证:

只有最后一次调用的结果会生效,之前的请求全部失效。

完整代码:

export type AnyAsyncFunction = (...args: any[]) => Promise<any>;

/**
 * 高阶函数:解决频繁请求的竞态问题
 */
export function createLatestRequest<T extends AnyAsyncFunction>(fn: T): T {
  let lastSeq = 0;

  return ((...args: Parameters<T>) => {
    const currentSeq = ++lastSeq;

    return fn(...args).then(
      (result) => {
        if (currentSeq !== lastSeq) {
          return new Promise<never>(() => {});
        }
        return result;
      },
      (error) => {
        if (currentSeq !== lastSeq) {
          return new Promise<never>(() => {});
        }
        throw error;
      },
    );
  }) as T;
}
1. 类型定义