为什么 Async Await 与 React setState 一起工作?

IT技术 reactjs async-await babeljs
2021-04-18 16:37:27

我一直在我的 ReactJS 项目中使用 async await 和 babel。我发现了 React setState 的一个方便用法,我只是想更好地理解它。考虑这个代码:

handleChange = (e) => {
  this.setState({[e.target.name]: e.target.value})
  console.log('synchronous code')
}

changeAndValidate = async (e) => {
  await this.handleChange(e)
  console.log('asynchronous validation code')
}

componentDidUpdate() {
  console.log('updated component')    
}

我的目的是让异步验证代码在组件更新后运行。它有效!结果控制台日志显示:

synchronous code
updated component
asynchronous validation code

只有在 handleChange 更新状态并呈现新状态后,验证代码才会运行。

通常要在状态更新后运行代码,您必须在 this.setState 之后使用回调。这意味着如果你想在 handleChange 之后运行任何东西,你必须给它一个回调参数,然后传递给 setState。不漂亮。但是在代码示例中,不知何故, await 在状态更新后知道 handleChange 已完成...但我认为 await 仅适用于Promise并在继续之前等待Promise解决。在 handleChange 中没有Promise也没有解决方案......它怎么知道要等待什么??

这意味着 setState 是异步运行的,而 await 以某种方式知道它何时完成。也许 setState 在内部使用了 Promise?

版本:

react:“^15.4.2”

babel 核心:“^6.26.0”

babel-preset-env: "^1.6.0",

babel-preset-react: "^6.24.1",

babel-preset-stage-0: "^6.24.1"

babel-plugin-system-import-transformer: "^3.1.0",

babel-plugin-transform-decorators-legacy: "^1.3.4",

babel-plugin-transform-runtime: "^6.23.0"

4个回答

我尽力简化和补充 Davin 的答案,以便您可以更好地了解这里实际发生的情况:


  1. await放置在this.handleChange 之前,这将安排changeAndValidate函数的其余部分的执行仅在await 解析其右侧指定的值时运行,在这种情况下是this.handleChange返回的值
  2. this.handleChange,在await的右边,执行:

    2.1. setState运行它的更新程序,但是因为setState不保证立即更新,它可能会安排更新在以后发生(无论是立即更新还是稍后时间点都没有关系,重要的是它已被调度)

    2.2. console.log('同步代码') 运行...

    2.3. this.handleChange然后退出返回undefined (返回 undefined 因为函数返回 undefined 除非明确指定)

  3. await然后接受这个undefined并且因为它不是一个Promise,所以它使用Promise.resolve(undefined)将它转换成一个已解决的Promise并等待它 - 它不是立即可用的,因为在幕后它被传递给它的异步方法.then

“在 JavaScript 事件循环的当前运行完成之前,永远不会调用传递给Promise的回调”

3.1. 这意味着undefined将被放置到事件队列的后面(这意味着它现在在事件队列中我们的 setState 更新程序后面......)

  1. 事件循环最终到达并获取我们的setState更新,它现在执行...

  2. 事件循环到达并拾取undefined,其计算结果为undefined (我们可以根据需要存储它,因此通常在 await 前面使用 = 来存储解析结果)

    5.1. Promise.resolve()现在完成了,这意味着await不再起作用,所以函数的其余部分可以恢复

  3. 您的验证代码运行
我不确定,这取决于所使用的更新算法,找不到足够的信息,但是,如果算法批量更新并在事件循环的当前周期结束时运行它们,那么应该保证
2021-06-09 16:37:27
所以只能靠运气了!:D
2021-06-13 16:37:27
在事件循环中, undefined 返回是否可靠地放置在 setState 更新之后?
2021-06-15 16:37:27

我还没有测试过这个,但这是我认为正在发生的事情:

undefined通过返回await后排队setState回调。Theawait正在做一个Promise.resolveunder(在regenerator-runtime),这反过来又将控制权交给事件循环中的下一个项目。

因此,setState回调恰好排在await.

您可以通过将 setTimeout(f => f, 0) 放在setState.

regenerator-runtimeinbabel本质上是一个Promise.resolve用于产生控制的循环你可以看到_asyncToGenerator的内部,它有一个Promise.resolve.

setTimeout 确实打破了它,但这是意料之中的。在任何Promise周围放置一个 setTimeout ,它会破坏异步等待,不是吗?但是,如果 setState 在所有其他情况下都像 async await 中的 promise,那么这仍然是一个可行的模式,不是吗?
2021-05-24 16:37:27
我的意思是,什么时候 await 在 setState 回调之前排队?
2021-05-27 16:37:27
@LeoFabrikantsetState的行为不像Promise。 await只是在setState回调后排队只有一个事件循环。
2021-05-28 16:37:27
这取决于setState回调相对于await. 但是使用awaitwith 会regenerator生成大量代码,这些代码从根本上改变了异步流程(但希望不是行为)。
2021-06-10 16:37:27
我不完全理解,因为我还没有完全理解事件循环以及事件如何排队,但这似乎是合乎逻辑的解释
2021-06-16 16:37:27

setState()并不总是立即更新组件文档

但这里可能就是这种情况。

如果你想用Promise替换回调,你可以自己实现:

setStateAsync(state) {
  return new Promise((resolve) => {
    this.setState(state, resolve)
  });
}

handleChange = (e) => {
  return this.setStateAsync({[e.target.name]: e.target.value})
}

参考:https : //medium.com/front-end-hacking/async-await-with-react-lifecycle-methods-802e7760d802

也许是因为它是立即更新的,并且在不是这种情况时不起作用。
2021-05-24 16:37:27
我不想改变任何东西,代码有效。在我的帖子下查看 Vlad 的评论,或者使用您自己的控制台日志进行尝试。只有在 this.setState 完成更新后, await 之后的任何代码才会暂停并运行!
2021-05-29 16:37:27
我明白你在说什么,当 setState 决定等待时,我的方法可能不起作用。测试一下可能是个好主意。但是 setState 仍然表现出奇怪的行为。我将在原文中添加另一个示例来演示这一点。
2021-06-07 16:37:27
it's not properly waiting for setState to complete. Your app just happens to finish setState quickly in that case, but will not for all cases 参考
2021-06-09 16:37:27
@Gabriel 你的命名 setStateAsync 相当混乱...... setState 不是已经异步了吗?您的函数是否更好地命名为 setStateSync,因为这里的目的是使其看起来同步。
2021-06-14 16:37:27

rvawaitor 返回值定义为:

rv
Returns the fulfilled value of the promise, or the value itself if it's not a Promise.

因此,由于 handleChange 不是异步或Promise值,它只是返回自然值(在这种情况下,没有返回,所以undefined)。因此,这里没有异步事件循环触发器来“让它知道 handleChange 已完成”,它只是按照您提供的顺序运行。

你会认为那会发生。但事实并非如此。Await 暂停代码,直到 setState 完成......然后运行内联后面的任何内容。用控制台日志试试
2021-06-08 16:37:27
我同意。只需将警报附加到渲染函数中的警报状态,您就会看到。
2021-06-20 16:37:27