【问题标题】:Asynchronous function keeps reading old value of global variable异步函数不断读取全局变量的旧值
【发布时间】:2021-02-15 20:06:48
【问题描述】:

我有以下 React 组件

    const [prjToFetch, setPrjToFetch] = React.useState([]);

    React.useEffect(() => {
    console.log("Before " + prjToFetch)
    async function func(){
      let myPromise = await new Promise(function(myResolve, myReject) {
        setTimeout(function() { myResolve("I love You !!"); }, 3000);
      });

      
      console.log("After: " + prjToFetch)
      return myPromise;
    }

    func()
  }, [prjToFetch])

而且我有一个按钮,只要按下它就会将值“a”推入数组 prjFetch:

const handleClick = () => {
  setPrjToFetch([...prjToFetch, "a"]);
}

如果我按下按钮一次,前面代码的输出是:

Before a
(3 seconds of idle time)
After a

这正是我所期望的。但是假设我连续按两次按钮,那么输出是:

Before a
Before a a
(3 second of idle time)
After a
After a a

为什么第一个“After”只返回“a”而不返回“a a”?

请注意,我还尝试将代码重写为:

  React.useEffect(() => {
    console.log("Before " + prjToFetch)
    async function func(){
      await new Promise(function(myResolve, myReject) {
        setTimeout(function() { myResolve("I love You !!"); }, 3000);
      });
    }

    func().then(() => {
      console.log("After: " + prjToFetch)
    })
  }, [prjToFetch])

但结果是一样的

【问题讨论】:

  • "异步函数一直读取全局变量的旧值;e" 变量不是全局的。如果是这样,您将看不到这种行为。 (但你不希望它是全球性的。)

标签: javascript reactjs async-await


【解决方案1】:

问题是func 关闭 prjToFetch 的值在func 被创建时,而您正在使用该值异步。当func 使用该值时,它已经过时了。

这是发生了什么:

  1. 组件挂载,触发对useEffect 回调的调用。
    1. 这会创建一个 func 的副本,该副本会覆盖当前的 prjToFetch 变量。
    2. 它调用func
    3. func 开始等待三秒钟
  2. 你点击按钮,触发prjToFetch的状态设置器
  3. 这使得组件函数再次运行,创建一组新的状态变量。由于 prjToFetch 已更改,React 再次调用您的 useEffect 回调
    1. 这会创建一个新的 func,它会关闭新的 prjToFetch 变量
    2. 它调用func
    3. func 开始等待三秒钟
  4. 第一个func 完成等待,并显示它关闭的prjToFetch 的值
  5. 第二个func 完成等待,并显示it 关闭的prjToFetch 的值(与前一个func 关闭的不同)李>

和这个完全一样:

let aInComponentState = 1;
function componentFunction() {
    const a = aInComponentState;
    console.log(`before: ${a}`);
    setTimeout(() => {
        console.log(`after: ${a}`);
    }, 1000);
}

componentFunction(); // component function called for mount
++aInComponentState; // button click updates state
componentFunction(); // component function called again because of state update

您可以从 useEffect 回调中返回一个函数,以便它知道组件已在上述步骤 2 和 3 之间重新渲染。

真正的解决方案会根据您实际想要做的事情而有所不同。但是如果您希望func 始终看到prjToFetch 的当前值,并且您希望两个func 实例都运行,则需要使用setPrjToFetch 设置器来访问当前值(这有点hacky):

setPrjToFetch(currentPrjToFetch => {
    console.log("After: " + currentPrjToFetch);
});

同样,这有点老套,通常会有更好的解决方案更具体地针对您实际使用 prjToFetch 的目的。


我建议阅读 Dan Abramov 的 A Complete Guide to useEffect。它可以帮助您理解useEffect,但也可以帮助您了解一般情况。

【讨论】:

  • 我能够解决我的问题。但我的印象是我可以编写更清晰的代码。我在stackoverflow.com/questions/66210673/… 中写了一个问题,确切地解释了我想要做什么,以及我是如何解决它的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-04-16
  • 2021-04-24
  • 2020-06-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多