当前位置: 首页 > 知识库问答 >
问题:

什么是正确的方法来取消使用效果钩子中的所有异步/等待任务,以防止响应中的内存泄漏?

东方森
2023-03-14

我正在一个反应章应用程序,从Firebase数据库中提取数据。在我的“仪表板”组件中,我有一个使用效果钩子检查身份验证的用户,如果是,从Firebase中提取数据,并设置电子邮件变量和聊天变量的状态。我使用的使用效果清理,但是每当我第一次注销和重新登录,我得到一个内存泄漏警告。

指数js:1375警告:无法对未安装的组件执行React状态更新。这是一个no-op,但它表示应用程序中存在内存泄漏。要修复此问题,请取消useEffect清理函数中的所有订阅和异步任务。

在仪表板(由上下文创建。消费者)

最初我没有abortController,我只是在清理时返回了一个控制台日志。做了更多的研究,发现abortController,但是示例使用fetch和signal,我找不到任何关于使用async/await的资源。我愿意改变检索数据的方式(无论是使用fetch、async/await还是任何其他解决方案),只是无法使用其他方法

const [email, setEmail] = useState(null);
const [chats, setChats] = useState([]);

const signOut = () => {
    firebase.auth().signOut();
  };

useEffect(() => {
    const abortController = new AbortController();
    firebase.auth().onAuthStateChanged(async _user => {
      if (!_user) {
        history.push('/login');
      } else {
        await firebase
          .firestore()
          .collection('chats')
          .where('users', 'array-contains', _user.email)
          .onSnapshot(async res => {
            const chatsMap = res.docs.map(_doc => _doc.data());
            console.log('res:', res.docs);
            await setEmail(_user.email);
            await setChats(chatsMap);
          });
      }
    });

    return () => {
      abortController.abort();
      console.log('aborting...');
    };
  }, [history, setEmail, setChats]);

预期结果是正确清理/取消useEffect清理函数中的所有异步任务。一个用户注销后,相同或不同的用户重新登录,我在控制台中收到以下警告

指数js:1375警告:无法对未安装的组件执行React状态更新。这是一个no-op,但它表示应用程序中存在内存泄漏。要修复此问题,请取消useEffect清理函数中的所有订阅和异步任务。

在仪表板(由上下文创建。消费者)

共有3个答案

邵绪
2023-03-14

取消async/await的一种方法是创建类似于内置AbortController的东西,它将返回两个函数:一个用于取消,一个用于检查取消,然后在async/await中的每个步骤之前都需要运行取消检查:

function $AbortController() {
  let res, rej;
  const p = new Promise((resolve, reject) => {
    res = resolve;
    rej = () => reject($AbortController.cSymbol);
  })
  
  function isCanceled() {
    return Promise.race([p, Promise.resolve()]);
  }
  
  return [
    rej,
    isCanceled
  ];
}

$AbortController.cSymbol = Symbol("cancel");

function delay(t) {
  return new Promise((res) => {
    setTimeout(res, t);
  })
}

let cancel, isCanceled;

document.getElementById("start-logging").addEventListener("click", async (e) => {
  try {
    cancel && cancel();
    [cancel, isCanceled] = $AbortController();
    const lisCanceled = isCanceled;
    while(true) {
      await lisCanceled(); // check for cancellation
      document.getElementById("container").insertAdjacentHTML("beforeend", `<p>${Date.now()}</p>`);
       await delay(2000);
    }
  } catch (e) {
    if(e === $AbortController.cSymbol) {
      console.log("cancelled");
    }
  }
})

document.getElementById("cancel-logging").addEventListener("click", () => cancel())
<button id="start-logging">start logging</button>
<button id="cancel-logging">cancel logging</button>
<div id="container"></div>
越飞鸾
2023-03-14

onSnapshot方法不会返回promise,因此等待其结果是没有意义的。相反,它开始侦听数据(以及对该数据的更改),并使用相关数据调用onSnapshot回调。这种情况可能会发生多次,因此它无法回报promise。侦听器保持与数据库的连接,直到您通过调用从onSnapshot返回的方法取消订阅它为止。由于您从未存储该方法,更不用说调用它了,侦听器将保持活动状态,稍后将再次调用您的回调。这可能是内存泄漏的原因。

如果您想等待Fi还原的结果,您可能正在寻找get()方法。这将获得一次数据,然后解决promise。

await firebase
      .firestore()
      .collection('chats')
      .where('users', 'array-contains', _user.email)
      .get(async res => {
彭骏
2023-03-14

对于firebase,您处理的不是async/await,而是流。您只需在清理功能中取消订阅firebase streams即可:

const [email, setEmail] = useState(null);
const [chats, setChats] = useState([]);

const signOut = () => {
    firebase.auth().signOut();
  };

useEffect(() => {
    let unsubscribeSnapshot;
    const unsubscribeAuth = firebase.auth().onAuthStateChanged(_user => {
        // you're not dealing with promises but streams so async/await is not needed here
      if (!_user) {
        history.push('/login');
      } else {
        unsubscribeSnapshot = firebase
          .firestore()
          .collection('chats')
          .where('users', 'array-contains', _user.email)
          .onSnapshot(res => {
            const chatsMap = res.docs.map(_doc => _doc.data());
            console.log('res:', res.docs);
            setEmail(_user.email);
            setChats(chatsMap);
          });
      }
    });

    return () => {
      unsubscribeAuth();
      unsubscribeSnapshot && unsubscribeSnapshot();
    };
  }, [history]); // setters are stable between renders so you don't have to put them here
 类似资料:
  • 我对Mono和Flux很陌生。我正在尝试加入几个下游API响应。这是一个传统的阻塞应用程序。我不想收集Mono的列表,我想要一个从下游API返回的有效负载列表,我从Mono获取。然而,有时返回给控制器的“结果”只有部分或没有下游API响应。正确的方法是什么?我读过几篇关于如何迭代通量和混合单态的帖子 您不应该在web应用程序中的任何位置调用subscribe。如果这是绑定到HTTP请求的,则基本上

  • 我有一组从基类继承的命令。基类有以下声明: 此异步方法缺少“await”运算符,将同步运行。考虑使用'await'运算符来等待非阻塞API调用,或者使用'await task.run(...)'在后台线程上执行CPU绑定的工作。 显式提供任务完成返回值是否正确?

  • 我们有一个async/await方法,它通过实体框架调用存储的过程,该框架由同步方法调用。 需要很长的时间来执行,这可能就是我们编写async/await的原因,它可以被多个地方使用。 我知道我们不应该混合异步和同步调用,但假设我们有这种情况并且我们正在使用 从同步方法 调用异步方法 GetLoanDataAsync,我理解该方法 - 将在后台线程上运行。 我的问题是,如果我们有一个异步方法< c

  • 我试图创建一个从服务器获取数据的函数,它工作了。但我不确定这是不是正确的方法? 我不确定是调用fetchData函数的地方。我在里面做那个有用吗?对地方?而且,这个电话只会发生一次?因为我用[]? 一般来说,你会怎么做这样的事情?

  • 我在实验在不同的关键字和运算符周围是如何解释的,发现以下语法是完全合法的: 错误: 未捕获的引用错误:等待未定义 它似乎试图将解析为变量名。。?我期待着 或者是类似于 意外令牌等待 令我恐惧的是,你甚至可以给它分配一些东西: 如此明显错误的东西不应该导致语法错误吗,就像,,等一样?为什么允许这样做,以及第一个片段中到底发生了什么?

  • 执行以下操作有什么不同: vs 在我的情况下,出于某种原因,只有第二种方法有效。第一个似乎永远不会结束。