每隔x秒轮询api并做出反应

时间:2017-09-10 12:37:36

标签: reactjs

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

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 }));
  }

这是正确的方法吗?

5 个答案:

答案 0 :(得分:21)

好吧,由于您只有一个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不完整,则取消。

我希望这会有所帮助。

答案 1 :(得分:14)

尽管有一个古老的问题,但当我搜索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>;
}

答案 2 :(得分:2)

@ AmitJS94,其中有一个有关如何停止间隔的详细部分,该间隔添加到GavKilbride提到的in this article方法上。

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

import React, {
  useEffect, useContext, useState, useRef,
} from 'react';
import PropTypes from 'prop-types';
import Player from '@vimeo/player';

import { BrowserContext } from '../../contexts/BrowserContext';

const TAG = 'player';

/**
 * remove event listeners
 * @param {object} player
 */
function removeEventListeners(player) {
  if (!player) return;
  player.off('ended');
  player.off('pause');
  player.off('play');
}
/**
 * remove interval
 * @param {number} interval
 */
function removeInterval(interval) {
  console.tag(TAG).debug('removeInterval called');
  window.clearInterval(interval);
}

/**
 * 640×480, 800×600, 960×720, 1024×768, 1280×960,
 * 1400×1050, 1440×1080 , 1600×1200, 1856×1392, 1920×1440, and 2048×1536
 * @param {number} width
 */
function computeRatio(delayedWidth, percentage = 0.9) {
  const height = window.innerHeight;
  const width = delayedWidth - (delayedWidth * (1 - percentage));

  if (height <= 480) {
    return width > 640 ? 640 : width;
  }
  if (height <= 600) {
    return width > 800 ? 800 : width;
  }
  if (height <= 720) {
    return width > 960 ? 960 : width;
  }
  if (height <= 768) {
    return width > 1024 ? 1024 : width;
  }
  if (height <= 960) {
    return width > 1280 ? 1280 : width;
  }
  if (height <= 1050) {
    return width > 1400 ? 1400 : width;
  }
  if (height <= 1080) {
    return width > 1440 ? 1440 : width;
  }
  if (height <= 1200) {
    return width > 1600 ? 1600 : width;
  }
  if (height <= 1392) {
    return width > 1856 ? 1856 : width;
  }
  if (height <= 1440) {
    return width > 1920 ? 1920 : width;
  }
  if (height <= 1536) {
    return width > 2048 ? 2048 : width;
  }
  return width;
}

const VideoPlayer = ({
  index, link, onProgress, latestProgress, widthPercentage, onVideoEnded,
}) => {
  const { delayedWidth } = useContext(BrowserContext);
  const [progress, setProgress] = useState(latestProgress < 1 ? latestProgress : 0);
  const playerRef = useRef(null);
  const intervalRef = useRef(null);

  useEffect(() => {
    console.tag(TAG).debug('changing delayed width', delayedWidth);

    const asyncEffect = async () => {
      const player = playerRef.current;
      if (player) {
        console.tag(TAG).debug('player detected, checking fullscreen');
        const isFullscreen = await player.getFullscreen();
        console.tag(TAG).debug('fullscreen detected', isFullscreen);

        if (isFullscreen) {
          return;
        }

        removeEventListeners(player);
        playerRef.current = null;
        player.pause(); // gets rid of interval
        player.destroy();
      }

      const options = { id: link, width: computeRatio(delayedWidth, widthPercentage) };
      const newPlayer = new Player(`frame-${index}`, options);
      playerRef.current = newPlayer;

      if (progress) {
        newPlayer.getDuration().then((duration) => {
          const seconds = duration * progress;
          newPlayer.setCurrentTime(seconds);
        });
      }

      const keepTrackProgress = async () => {
        // gets duration of video in seconds
        const duration = await newPlayer.getDuration();

        intervalRef.current = window.setInterval(() => {
          const currentPlayer = playerRef.current;
          if (!currentPlayer) {
            return;
          }
          currentPlayer.getCurrentTime().then((seconds) => {
            // `seconds` indicates the current playback position of the video
            const newProgress = seconds / duration;
            console.tag(TAG).debug(`progress: ${newProgress}, duration ${duration}, seconds ${seconds}`);
            onProgress(newProgress);
            setProgress(newProgress);
          });
          // track every next 10 seconds of progress
        }, 10000);
      };

      newPlayer.on('ended', () => {
        console.tag(TAG).debug('player onEnded');
        removeInterval(intervalRef.current);
        intervalRef.current = null;
        onProgress(1);
        setProgress(1);
        onVideoEnded();
      });

      newPlayer.on('pause', ({ duration, seconds }) => {
        console.tag(TAG).debug('player onPause');
        removeInterval(intervalRef.current);
        intervalRef.current = null;
        const newProgress = seconds / duration;
        console.tag(TAG).debug(`progress at paused: ${newProgress}, duration ${duration}, seconds ${seconds}`);
        onProgress(newProgress);
        setProgress(newProgress);
      });

      newPlayer.on('play', () => {
        console.tag(TAG).debug('player onPlay');
        keepTrackProgress();
      });
    };

    asyncEffect();
  }, [delayedWidth]);

  useEffect(() => () => {
    removeInterval(intervalRef.current);
    removeEventListeners(playerRef.current);
    if (playerRef.current) {
      playerRef.current.destroy();
    }
  }, []);

  return (
    <div id={`frame-${index}`} className="frame-wrapper" />
  );
};


VideoPlayer.propTypes = {
  index: PropTypes.number.isRequired,
  link: PropTypes.string.isRequired,
  onProgress: PropTypes.func.isRequired,
  onVideoEnded: PropTypes.func,
  latestProgress: PropTypes.number.isRequired,
  widthPercentage: PropTypes.number,
};

VideoPlayer.defaultProps = {
  widthPercentage: 0.9,
  onVideoEnded: () => {},
};

export default VideoPlayer;

一定要仔细阅读这篇文章,以更好地了解细节-超级透彻,写得很好!

答案 3 :(得分:1)

您可以结合使用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 :(得分:0)

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

  • 每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);
     }