简体   繁体   English

如何在点击时停止递归 function?

[英]How can I stop a recursing function on click?

So I have a function that fetches from an API until it hits a 200 code.所以我有一个 function,它从 API 中获取,直到它达到 200 代码。

Something like this:像这样:

/* These functions are in a separate file */
const fetchFile = async () => {
 try {
   const resp = await fetch('someUrl');
   return resp.data;
 } catch (err) {
    if(err.response.code === 404){
      await sleep(10); //sleep is a custom fn that awaits for N seconds
      return fetchFile();
    }else{
      return 'Error';
    }
 }
}

const fetchAll = async (setData, setError) => {
  const data = await fetchFile();
  if(data === 'Error') {
    setError('Sorry, error ocurred');
    return;
  }
  setData(data);
}




/* React component */
const [data, setData] = useState([]);
const [error, setError] = useState('');
<button onClick={fetchAll}>Start fetch</button>

And I need to have another button that let's the user stop the recursing function. I know a common way of doing this is having a flag and check the value every time I call the recursing function, but since this is React, how can I achieve this?我需要另一个按钮让用户停止递归 function。我知道这样做的一种常见方法是有一个标志并在每次调用递归 function 时检查值,但由于这是 React,我该如何实现这个? Should I have the functions inside the component file?我应该在组件文件中包含函数吗? Use a global window variable?使用全局 window 变量? Use localstorage?使用本地存储? What's the best way?最好的方法是什么?

You can do couple of ways, here is one the way using useRef您可以采用多种方式,这是使用 useRef 的一种方式

/* These functions are in a separate file */ /* 这些函数在一个单独的文件中 */

const fetchFile = async (cancellationRef) => {
 try {
   const resp = await fetch('someUrl');
   return resp.data;
 } catch (err) {
    if(err.response.code === 404){
       if(cancellationRef.current !==true){
         await sleep(10); //sleep is a custom fn that awaits for N seconds
         return fetchFile();
        }
        else{
           return 'Request cancelled';
        }
    }else{
      return 'Error';
    }
 }
}

const fetchAll = async (setData, setError,cancellationRef) => {
  const data = await fetchFile(cancellationRef);
  if(data === 'Error') {
    setError('Sorry, error ocurred');
    return;
  }
  setData(data);
}




/* React component */
const [data, setData] = useState([]);
const cancellationRef = useRef(false);
const [error, setError] = useState('');
<button onClick={fetchAll}>Start fetch</button>
  <button onClick={()=>cancellationRef.current =true)}>Stop looping</button>

Here is a full example - I recommend encapsulating the fetching behavior in a hook like this.这是一个完整的示例——我建议将抓取行为封装在这样的钩子中。 An AbortController is used for cancelling any ongoing request. AbortController用于取消任何正在进行的请求。 This takes advantage of useEffect and its cleanup function, which is described in the react docs :这利用了useEffect及其清理 function,这在反应文档中有描述:

If your effect returns a function, React will run it when it is time to clean up:如果您的效果返回 function,React 将在需要清理时运行它:

/** Continuously makes requests until a non-404 response is received */
const fetchFile = async (url, signal) => {
    try {
        const resp = await fetch(url, { signal });
        return resp.data;
      } catch (err) {
         if(err.response.code === 404){
           await sleep(10);
           return fetchFile(url, signal);
         }
         // there was an actual error - rethrow
         throw err;
      }
}

/** Hook for fetching data and cancelling the request */
const useDataFetcher = (isFetching) => {
    // I advise using `null` as default values
    const [data, setData] = useState(null);
    const [error, setError] = useState(null);

    useEffect(() => {
        if (isFetching) {
            const controller = new AbortController();
            fetchFile('someUrl', controller.signal)
                .then(result => {
                    setData(result);
                    setError(null);
                })
                .catch(err => {
                    if (err.name === "AbortError") {
                        // request was aborted, reset state
                        setData(null);
                        setError(null);
                    } else {
                        setError(err);
                    }
                });

            // This cleanup is called every time the hook reruns (any time isFetching changes)
            return () => {
                if (!controller.signal.aborted) {
                    controller.abort();
                }
            }
        }        
    }, [isFetching]);

    return { data, error };
}

const MyComponent = () => {
    const [isFetching, setIsFetching] = useState(false);
    const { data, error } = useDataFetcher(isFetching);

    const startFetch = useCallback(() => setIsFetching(true), []);
    const cancelFetch = useCallback(() => setIsFetching(false), []);

    useEffect(() => {
        if (data || error) {
            setIsFetching(false);
        }
    }, [data, error];

    return isFetching
        ? <button onClick={cancelFetch}>Cancel</button>
        : <button onClick={startFetch}>Start fetch</button>
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM