【问题标题】:Avoiding React Race condition with AbortController not working使用 AbortController 避免 React Race 条件不起作用
【发布时间】:2021-07-06 05:40:43
【问题描述】:

我正在尝试模仿 React useEffect 竞争条件并使用 AbortController 处理它。我永远无法击中 catch 块(我猜),因为 setTimeOut 在获取请求后被调用。我的问题是如何重写此代码以将 fetch 放入 setTimeout 并且仍然能够使用 AbortController 取消请求?

import './App.css';
import {useState,useEffect} from 'react'

function App() {

  const [country, setCountry] = useState('N.A.')
  const [capital, setCapital] = useState('')
 
  
useEffect(() => {      

  const ctrl = new AbortController(); 
  const load = async() =>{
     try
       {
         //debugger
          const response = await fetch(`https://restcountries.eu/rest/v2/capital/${capital}`,
          {signal:ctrl.signal})
          const jsonObj = await response.json()
          setTimeout( ()=> {setCountry(jsonObj[0].name)} , Math.random()*10000)    
       }
    catch(err)
    {
      console.log(err)
    }  
 }
  load();
  
  return () =>{              
              ctrl.abort() 
            };

}, [capital])

  
  return (
    <div>
       <button  onClick={()=>setCapital("Berlin")}  >Berlin</button>      
       <button  onClick={()=>setCapital("Paris")}  >Paris</button>      
       <button  onClick={()=>setCapital("Madrid")}  >Madrid</button>   
       <div> 
         {country}
      </div>  
    </div>
  );
}

export default App;

【问题讨论】:

  • 这里有需要使用 setTimeout 的理由吗?在我看来,删除那个 setTimeout 可以解决你的问题。

标签: reactjs use-effect


【解决方案1】:

嗯...只需将该函数放在 setTimeout 调用中,不要忘记在卸载时清理计时器 (Demo)。

import React, { useState, useEffect } from "react";

export default function TestComponent(props) {
  const [country, setCountry] = useState("N.A.");
  const [capital, setCapital] = useState("");
  const [error, setError] = useState(null);

  useEffect(() => {
    let isMounted = true;

    const ctrl = new AbortController();
    const timer = setTimeout(async () => {
      try {
        if (!capital) {
          return;
        }
        const response = await fetch(
          `https://restcountries.eu/rest/v2/capital/${capital}`,
          { signal: ctrl.signal }
        );
        // if (!isMounted) return; // can be omitted here
        const jsonObj = await response.json();

        isMounted && setCountry(jsonObj[0].name);
      } catch (err) {
        console.log(err);
        isMounted && setError(err);
      }
    }, Math.random() * 10000);

    return () => {
      clearTimeout(timer);
      isMounted = false;
      ctrl.abort();
    };
  }, [capital]);

  return (
    <div className="component">
      <div className="caption">useAsyncEffect demo:</div>
      <button onClick={() => setCapital("Berlin")}>Berlin</button>
      <button onClick={() => setCapital("Paris")}>Paris</button>
      <button onClick={() => setCapital("Madrid")}>Madrid</button>
      <div>Country: {error ? <b>{error.toString()}</b> : country}</div>
    </div>
  );
}

或者您也可以使用自定义库 (Demo):

import React, { useState } from "react";
import { useAsyncEffect, E_REASON_UNMOUNTED } from "use-async-effect2";
import { CPromise, CanceledError } from "c-promise2";
import cpFetch from "cp-fetch";

export default function TestComponent(props) {
  const [country, setCountry] = useState("N.A.");
  const [capital, setCapital] = useState("");
  const [error, setError] = useState(null);

  const cancel = useAsyncEffect(
    function* () {
      setError(null);
      if (!capital) {
        return;
      }
      yield CPromise.delay(Math.random() * 10000);
      try {
        const response = yield cpFetch(
          `https://restcountries.eu/rest/v2/capital/${capital}`
        ).timeout(props.timeout);

        const jsonObj = yield response.json(); 
        setCountry(jsonObj[0].name);
      } catch (err) {
        CanceledError.rethrow(err, E_REASON_UNMOUNTED);
        console.log(err);
        setError(err);
      }
    },
    [capital]
  );

  return (
    <div className="component">
      <div className="caption">useAsyncEffect demo:</div>
      <button onClick={() => setCapital("Berlin")}>Berlin</button>
      <button onClick={() => setCapital("Paris")}>Paris</button>
      <button onClick={() => setCapital("Madrid")}>Madrid</button>
      <button onClick={cancel}>Cancel request</button>
      <div>Country: {error ? <b>{error.toString()}</b> : country}</div>
    </div>
  );
}

【讨论】:

  • 感谢 Dmitriy,第一个演示不起作用...检查第二个!欢呼
  • @punjabi 刚刚修复了第一个示例。看来我是从错误的沙箱中复制了代码,而不是从上一个分支中复制的。
  • 谢谢@dmitriy Mozgovoy ...你有推特吗?我想在我正在制作的视频中提及你
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-10-23
  • 1970-01-01
  • 2017-07-16
  • 2017-07-09
  • 1970-01-01
  • 2017-03-22
  • 2018-09-21
相关资源
最近更新 更多