在React中处理API请求并行 race http/fetch/axios/ajax 的情况

实际业务中不可避免的有网络请求的情况需要处理
更新于: 2022-09-21 06:09:24

开局

开局一群狗

场景

假设一个有网络请求的组件,在页面切换的情况下,导致多次请求

list → detail/1

list → detail/2

list → detail/3

网络延时:有可能因为网络延时,出错等场景,我们在 detail/3 的时候,会闪现 detail/2 的数据

网各出错:有可能停留在上一个页面的数据,我们页面已经切到了 detail/3 但数据还在 detail/1 这里

先看2种错误的示范

const StarwarsHero = ({ id }) => {
  const [data, setData] = useState(null);

  useEffect(() => {
    setData(null);

    fetchStarwarsHeroData(id).then(
      result => setData(result),
      e => console.warn('fetch failure', e),
    );
  }, [id]);

  return <div>{data ? data.name : <Spinner />}</div>;
};
class StarwarsHero extends React.Component {
  state = { data: null };

  fetchData = id => {
    fetchStarwarsHeroData(id).then(
      result => setState({ data: result }),
      e => console.warn('fetch failure', e),
    );
  };

  componentDidMount() {
    this.fetchData(this.props.id);
  }

  componentDidUpdate(prevProps) {
    if (prevProps.id !== this.props.id) {
      this.fetchData(this.props.id);
    }
  }

  render() {
    const { data } = this.state;
    return <div>{data ? data.name : <Spinner />}</div>;
  }
}

分析问题

  • useEffect 的时候,需要 abort 请求,否则会有异步带来的一系列问题
  • 当 aborted 之后,后面的 setData 不要再继续执行了,否则也有可能会有错误情况

正确方案

useEffect(() => {
  setData(null);

  // Create the current request's abort controller
  const abortController = new AbortController();

  // Issue the request
  fetchStarwarsHeroData(id, {
    signal: abortController.signal,
  })
    // Simulate some delay/errors
    .then(async data => {
      await delayRandomly();
      throwRandomly();
      return data;
    })
    // Set the result, if not aborted
    .then(
      result => {
        // IMPORTANT: we still need to filter the results here,
        // in case abortion happens during the delay.
        // In real apps, abortion could happen when you are parsing the json,
        // with code like "fetch().then(res => res.json())"
        // but also any other async then() you execute after the fetch
        if (abortController.signal.aborted) {
          return;
        }
        setData(result);
      },
      e => console.warn('fetch failure', e),
    );

  // Trigger the abortion in useEffect's cleanup function
  return () => {
    abortController.abort();
  };
}, [id]);

参考