【问题标题】:Polling API every x seconds with react每 x 秒轮询 API 并做出反应
【发布时间】:2018-02-18 19:31:44
【问题描述】:

我必须每隔一两秒监控屏幕上的一些数据更新信息。 我认为使用此实现的方式:

    componentDidMount() {
        this.timer = setInterval(()=> this.getItems(), 1000);
      }
    
      componentWillUnmount() {
        this.timer = null;
      }
    
      getItems() {
        fetch(this.getEndpoint('api url endpoint"))
            .then(result => result.json())
            .then(result => this.setState({ items: result }));
      }

这是正确的方法吗?

【问题讨论】:

  • 这是一种方法,但效率低下,并且会在您扩展应用程序时使您的服务器过载。如果使用socket连接,可以在消息到达时得到通知,效率会高很多
  • 我只有 REST API 可以使用...我怎样才能以正确的方式进行这种“池化”?
  • 这取决于您的服务器中使用的技术。你应该阅读一下 web sockets 是如何工作的,这里有一篇文章 blog.teamtreehouse.com/an-introduction-to-websockets 但是周围有很多资源可以玩

标签: javascript reactjs polling


【解决方案1】:

好吧,既然您只有一个 API,并且无法控制它以将其更改为使用套接字,那么您唯一的方法就是轮询。

就您的民意调查而言,您的做法不错。但是上面的代码中有一个问题。

componentDidMount() {
  this.timer = setInterval(()=> this.getItems(), 1000);
}

componentWillUnmount() {
  this.timer = null; // here...
}

getItems() {
  fetch(this.getEndpoint('api url endpoint"))
    .then(result => result.json())
    .then(result => this.setState({ items: result }));
}

这里的问题是,一旦你的组件卸载,虽然你存储在this.timer 中的间隔引用设置为null,它还没有停止。即使在您的组件已卸载后,该间隔仍将继续调用处理程序,并将尝试在不再存在的组件中 setState

要正确处理它,请先使用clearInterval(this.timer),然后再设置this.timer = null

另外,fetch 调用是异步的,这可能会导致同样的问题。将其设为cancelable 并在任何fetch 不完整时取消。

我希望这会有所帮助。

【讨论】:

  • 如果您演示了如何进行您提议的更改,那就太好了。
  • 小心使用 setInterval() 进行异步调用,因为即使它正在等待 API 响应,它也会调用。更安全的调用是使用带有递归的 setTimeout()。
  • 此处添加了@GustavoGarcia 评论的工作示例 - stackoverflow.com/a/63134447/5618143
【解决方案2】:

虽然这是一个老问题,但当我搜索 React Polling 并且没有与 Hooks 一起使用的答案时,它是最重要的结果。

// utils.js

import React, { useState, useEffect, useRef } from 'react';

export const useInterval = (callback, delay) => {

  const savedCallback = useRef();

  useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);


  useEffect(() => {
    function tick() {
      savedCallback.current();
    }
    if (delay !== null) {
      const id = setInterval(tick, delay);
      return () => clearInterval(id);
    }
  }, [delay]);
}

来源:https://overreacted.io/making-setinterval-declarative-with-react-hooks/

然后您就可以导入并使用了。

// MyPage.js

import useInterval from '../utils';

const MyPage = () => {

  useInterval(() => {
    // put your interval code here.
  }, 1000 * 10);

  return <div>my page content</div>;
}

【讨论】:

  • 我们如何取消 useInterval ?我的意思是在任何情况下都停止投票
  • 设置时间为0取消定时器
  • 如何在打字稿中制作这个?
  • 这个钩子的Typescript NPM包:npmjs.com/package/@use-it/interval
  • 如何在轮询后得到想要的响应后取消定时器?尝试延迟添加0。它不断调用 API。
【解决方案3】:

您可以使用setTimeoutclearTimeout 的组合。

setInterval 将每隔 'x' 秒触发一次 API 调用,无论之前的调用是成功还是失败。随着时间的推移,这会占用您的浏览器内存并降低性能。此外,如果服务器宕机,setInterval 会继续轰炸服务器而不知道它的宕机状态。

然而,

您可以使用setTimeout 进行递归。仅当先前的 API 调用成功时才触发后续的 API 调用。如果之前的调用失败,请清除超时并且不要触发任何进一步的调用。如果需要,在失败时提醒用户。让用户刷新页面重新开始这个过程。

这是一个示例代码:

let apiTimeout = setTimeout(fetchAPIData, 1000);

function fetchAPIData(){
    fetch('API_END_POINT')
    .then(res => {
            if(res.statusCode == 200){
                // Process the response and update the view.
                // Recreate a setTimeout API call which will be fired after 1 second.
                apiTimeout = setTimeout(fetchAPIData, 1000);
            }else{
                clearTimeout(apiTimeout);
                // Failure case. If required, alert the user.
            }
    })
    .fail(function(){
         clearTimeout(apiTimeout);
         // Failure case. If required, alert the user.
    });
}

【讨论】:

    【解决方案4】:

    @AmitJS94,有一个详细的部分介绍了如何停止添加到 GavKilbride 提到的方法上的间隔 in this article

    作者说要为延迟变量添加一个状态,并在您想要暂停间隔时为该延迟传递“null”:

    const [delay, setDelay] = useState(1000);
    const [isRunning, setIsRunning] = useState(true);
      useInterval(() => {
        setCount(count + 1);
      }, isRunning ? delay : null);
    
        useEffect(() => {
        function tick() {
          savedCallback.current();
        }
    
        if (delay !== null) {
          let id = setInterval(tick, delay);
          return () => clearInterval(id);
        }
      }, [delay]);
    

    一定要阅读这篇文章以更好地了解细节——它超级透彻而且写得很好!

    【讨论】:

      【解决方案5】:

      正如 Vasanth 提到的,我更喜欢:

      • 使用 setTimeout 测量从上一个请求结束到下一个请求开始之间的时间
      • 立即发出第一个请求,而不是延迟之后
      • 灵感来自@KyleMit https://stackoverflow.com/a/64654157/343900 的回答
      import { useEffect, useRef } from 'react';
      
      export const useInterval = (
        callback: Function,
        fnCondition: Function,
        delay: number,
      ) => {
        const savedCallback = useRef<Function>();
        useEffect(() => {
          savedCallback.current = callback;
        }, [callback]);
        useEffect(() => {
          let id: NodeJS.Timeout;
          const tick = async () => {
            try {
              const response =
                typeof savedCallback.current === 'function' &&
                (await savedCallback.current());
              if (fnCondition(response)) {
                id = setTimeout(tick, delay);
              } else {
                clearTimeout(id);
              }
            } catch (e) {
              console.error(e);
            }
          };
          tick();
          return () => id && clearTimeout(id);
          // eslint-disable-next-line react-hooks/exhaustive-deps
        }, [delay]);
      };
      

      WORKS:在其中使用 fnCondition 可以是基于上一个请求的响应的条件。

      //axios-hooks
      const {
          data,
          isLoadingData,
          getData,
      } = api.useGetData();
      
      const fnCondition = (result: any) => {
          const randomContidion = Math.random();
          //return true to continue
          return randomContidion < 0.9;
        };
      useInterval(() => getData(), fnCondition, 1000);
      

      不工作:将 delay 作为 null 以停止 useInterval 像这样 不起作用对我来说 使用此代码:https://www.aaron-powell.com/posts/2019-09-23-recursive-settimeout-with-react-hooks/

      (你可能会觉得它可以工作,但在启动/停止几次后它就坏了)

        const [isRunning, setIsRunning] = useState(true);
        const handleOnclick = () => {
          setIsRunning(!isRunning);
        };
      
        useInterval(() => getData(), isRunning ? 1000 : null);
      
        <button onClick={handleOnclick}>{isRunning ? 'Stop' : 'Start'}</button>
      

      总结:我可以通过传递 fnCondition 来停止 useInterval,但不能通过传递 delay=null

      【讨论】:

      • 你知道为什么要将回调保存在 ref 中吗?
      【解决方案6】:

      这是一个简单而完整的解决方案:

      • 每 X 秒轮询一次

      • 可以选择在每次逻辑运行时增加超时,以免服务器过载

      • 在最终用户退出组件时清除超时

         //mount data
         componentDidMount() {
             //run this function to get your data for the first time
             this.getYourData();
             //use the setTimeout to poll continuously, but each time increase the timer
             this.timer = setTimeout(this.timeoutIncreaser, this.timeoutCounter);
         }
        
         //unmounting process
         componentWillUnmount() {
             this.timer = null; //clear variable
             this.timeoutIncreaser = null; //clear function that resets timer
         }
        
         //increase by timeout by certain amount each time this is ran, and call fetchData() to reload screen
         timeoutIncreaser = () => {
             this.timeoutCounter += 1000 * 2; //increase timeout by 2 seconds every time
             this.getYourData(); //this can be any function that you want ran every x seconds
             setTimeout(this.timeoutIncreaser, this.timeoutCounter);
         }
        

      【讨论】:

        猜你喜欢
        • 2020-10-31
        • 1970-01-01
        • 1970-01-01
        • 2012-03-05
        • 2021-03-29
        • 2012-04-04
        • 1970-01-01
        • 2021-03-18
        • 2023-03-29
        相关资源
        最近更新 更多