【问题标题】:How to Cancel subscription in async promise to avoid memory leak in Reactjs如何在异步承诺中取消订阅以避免 Reactjs 中的内存泄漏
【发布时间】:2020-01-29 15:20:32
【问题描述】:

在我的 React 组件中,我有一个 async 请求,它向我的 Redux 存储分派一个操作,该存储在 useEffect 钩子中调用:

    const loadFields = async () => {
        setIsLoading(true);
        try {
            await dispatch(fieldsActions.fetchFields(user.client.id));
        } catch (error) {
            setHasError(true);
        }
        setIsLoading(false);
    }
    useEffect(() => { if(isOnline) { loadFields() } }, [dispatch, isOnline]);

动作通过获取请求请求数据:

    export const fetchFields = clientId => {
        return async dispatch => {
            try {
                const response = await fetch(
                    Api.baseUrl + clientId + '/fields',
                    { headers: { 'Apiauthorization': Api.token } }
                );

                if (!response.ok) {
                    throw new Error('Something went wrong!');
                }

                const resData = await response.json();
                dispatch({ type: SET_FIELDS, payload: resData.data });
            } catch (error) {
                throw error;
            }
        }
    };

    export const setFields = fields => ({
        type    : SET_FIELDS,
        payload : fields
    });

当它在 React 应用程序中呈现时,会导致以下警告:

Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in %s.%s, a useEffect cleanup function

我相信这是因为 Promise 没有“清理”功能。但我不确定在哪里放置这个?我应该在LoadFields() 中有一些逻辑吗?还是必须在 useEffect 挂钩内完成?

【问题讨论】:

  • 您的问题是否重复? stackoverflow.com/questions/38329209/…
  • 并非如此,因为这个使用的是 react-hooks + http 调用本身没有问题
  • @chin8628 我同意@oktapodia 的观点,因为这个问题也特别与 axios 有关。我的问题是理解删除useEffect 中的订阅作为async 函数的一部分。

标签: javascript reactjs promise


【解决方案1】:

tutorial 将帮助您解决问题。

快速示例:使用 Promises

function BananaComponent() {

  const [bananas, setBananas] = React.useState([])

  React.useEffect(() => {
    let isSubscribed = true
    fetchBananas().then( bananas => {
      if (isSubscribed) {
        setBananas(bananas)
      }
    })
    return () => isSubscribed = false
  }, []);

  return (
    <ul>
    {bananas.map(banana => <li>{banana}</li>)}
    </ul>
  )
}

快速示例:使用 async/await(不是最好的,但应该与匿名函数一起使用)

function BananaComponent() {

  const [bananas, setBananas] = React.useState([])

  React.useEffect(() => {
    let isSubscribed = true
    async () => {
      const bananas = await fetchBananas();
      if (isSubscribed) {
        setBananas(bananas)
      }
    })();

    return () => isSubscribed = false
  }, []);

  return (
    <ul>
    {bananas.map(banana => <li>{banana}</li>)}
    </ul>
  )
}

【讨论】:

  • 感谢您的资源。在该示例中,他们使用.then() 在完成fetch() 调用后应用该操作。当我在useEffect 中使用await 关键字作为useDispatch 时,取消订阅的逻辑在哪里存在?
【解决方案2】:

第一期
如果您的useEffect() 异步获取数据,那么最好有一个清理功能来取消未完成的fetch。否则可能发生的事情是这样的:fetch 花费的时间比预期的要长,同时无论出于何种原因,组件都会重新渲染。可能是因为它的父级被重新渲染。 useEffect 的清理在重新渲染之前运行,useEffect 本身在重新渲染之后运行。为避免在机上出现另一个fetch,最好取消前一个。示例代码:

const [data, setData] = useState();
useEffect(() => {
  const controller = new AbortController();
  const fetchData = async () => {
    try {
      const apiData = await fetch("https://<yourdomain>/<api-path>",
                               { signal: controller.signal });
      setData(apiData);
    } catch (err) {
      if (err.name === 'AbortError') {
        console.log("Request aborted");
        return;
      }
    }
  };

  fetchData();
  return () => {
    controller.abort();
  }
});

第二期
这段代码

return async dispatch => {

将不起作用,因为 dispatch 和 Redux 存储都不支持异步操作。处理这个问题最灵活和最强大的方法是使用像redux-saga 这样的中间件。中间件让您:

  • 将“常规”同步操作发送到 Redux 存储。
  • 拦截这些同步操作并作为响应进行一个或多个异步调用,做任何你想做的事情。
  • 等到异步调用完成并作为响应将一个或多个同步操作分派到 Redux 存储区,可以是您拦截的原始操作,也可以是不同的操作。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-06-12
    • 2014-07-31
    • 1970-01-01
    • 1970-01-01
    • 2019-03-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多