• 如何避免旧请求的数据覆盖掉最新请求


    我的博客地址:如何避免旧请求的数据覆盖掉最新请求 - 蚊子的前端博客

    在检索的场景中,经常会对同一个接口发起不同的检索条件的请求,若前一个请求响应较慢时,可能会覆盖掉我们后发起请求的结果。

    如我们先发起一个搜索请求,参数是 A;这个请求还没结束,我们发起了参数是 B 的搜索请求;可能因网络原因或者后端服务处理等原因,后发起的参数 B 的请求先响应了,然后我们把数据展示到页面中;过一会儿之前发起参数是 A 的搜索请求也返回结果了。但实际上,参数 B 的响应结果,才是我们需要展示的最新的数据。

    那么如何避免这种现象呢?

    1. 请求锁定

    可以对发起请求的按钮、输入框等,或者是在全局中,添加 loading,只有得到上一个请求的响应结果后,才取消 loading,才允许用户发起下一次的请求。

    const App = () => {
      const [loading, setLoading] = useState(false);
    
      const request = async (data) => {
        if (loading) {
          // 若请求还没结束,则无法发起新的请求
          return;
        }
        setLoading(true);
        const result = await axios("/api", { data, method: "post" });
        setLoading(false);
      };
    
      return (
        <div className="app">
          <Form disabled={loading} onFinish={request}>
            <Form.Item>
              <Input />
            Form.Item>
            <Button htmlType="submit" loading={loading}>
              搜索
            Button>
          Form>
        div>
      );
    };
    

    直接用源头进行控制,根本不存在多个请求并行的情况,也就无所谓谁覆盖谁了。

    2. 防抖

    一般应用在纯输入框的搜索功能中,在用户停止输入一段时间后,才发起搜索,可以加大两次检索请求之间的时间间隔.

    const App = () => {
      const request = async () => {};
    
      return (
        <div className="app">
          {/* 停止输入700ms后触发请求 */}
          <input onInput={debounce(request, 700)} />
        div>
      );
    };
    

    防抖措施并不能完全杜绝数据被覆盖。假如上一次的请求确实很慢,那也会出现覆盖后续请求的现象。

    3. 取消上次的请求

    当需要发起新的请求,上次的请求还没结束时,可以取消上次请求。

    const App = () => {
      const cancelSouceRef = useRef(null);
    
      const request = async () => {
        if (cancelSouceRef.current) {
          // 若存在上次的请求还没结束,则手动取消
          cancelSouceRef.current.cancel("手动取消上次的请求");
        }
        const source = axios.CancelToken.source();
        cancelSouceRef.current = source;
    
        try {
          const response = await axios.get("/api/data", {
            cancelToken: source.token,
          });
          setData(response.data);
        } catch (error) {
          if (axios.isCancel(error)) {
            console.log("请求被取消", error.message);
          } else {
            console.log("请求出错", error.message);
          }
        } finally {
          cancelSouceRef.current = null;
        }
      };
    
      return (
        <div className="app">
          <button onClick={request}>请求button>
        div>
      );
    };
    

    如果服务端已接收到了请求,不论前端是否取消了请求,服务端都会完整查询并返回,只是浏览器不再处理而已。

    4. 时序控制

    我们在每次请求时,都给该请求一个唯一标识,并在该组件的全局中保存最新的标识,若响应时的标识表示最新的标识,则不处理该响应的结果。

    标识只要在当前组件中唯一即可,如自增数字、时间戳、随机数等,都可以。

    const App = () => {
      const requestIdRef = useRef(0); // 保存最新的请求id
    
      const request = async () => {
        requestIdRef.current++; // 每次自增
    
        const curRequestId = requestIdRef.current;
        try {
          const response = await axios.get("/api/data", {
            cancelToken: source.token,
          });
          if (curRequestId < requestIdRef.current) {
            // 当前请求不是最新的,不做任何处理
            return;
          }
          setData(response.data);
        } catch (error) {
          console.error(error);
        }
      };
    
      return (
        <div className="app">
          <button onClick={request}>请求button>
        div>
      );
    };
    

    这是一种比较简单有效,同时能让用户任意搜索的方案。

    5. 总结

    当然,在实际中,肯定也是多方案的组合。比如纯输入框触发搜索的场景中,一般是防抖+时序控制的两种方案的组合,既能减少触发请求的次数,又能避免数据的相互覆盖。

    有的同学可能想到「控制请求的并发数量」,用队列递归等方式,每次将发起的请求都放到队列的后面,然后按照队列的顺序发起请求。如我们之前曾经在文章 JavaScript 中的 Promise 异步并发控制 探讨过这种场景。

    这种方式倒也能解决问题,不过有种「杀鸡用牛刀」的感觉。因为在现在的场景中,对同一个接口发起多次请求时,其实我们更关心的是最新请求的结果,之前请求的结果直接可以扔掉了。

    欢迎关注我的公众号:前端小茶馆。

    前端小茶馆
  • 相关阅读:
    NAFNet(ECCV 2022)-图像修复论文解读
    滚珠丝杆的工作原理是什么?
    SPFA和链式前向星
    对mysql的联合索引的深刻理解
    Invalid prop: custom validator check failed for prop “percentage
    java面试题整理《redis篇》九
    spring整合struts2 以及 会遇到的问题
    舞伴问题代码
    有用的CSS代码块
    大模型日报2024-06-15
  • 原文地址:https://www.cnblogs.com/xumengxuan/p/18414021