为啥 Async Await 可以与 React setState 一起使用?

Posted

技术标签:

【中文标题】为啥 Async Await 可以与 React setState 一起使用?【英文标题】:Why does Async Await work with React setState?为什么 Async Await 可以与 React setState 一起使用? 【发布时间】:2018-04-11 15:58:43 【问题描述】:

我一直在我的 ReactJS 项目中使用带有 babel 的 async await。我发现了一个方便的 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 中没有承诺也没有解决方案...它怎么知道要等待什么?

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

版本:

反应:“^15.4.2”

babel-core: "^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"

【问题讨论】:

上面的代码在undefined 上有一个await(因为handleChange 不返回任何内容)。你确定它对setState有影响吗? 你用的是什么版本的 React 和 babel? 这很奇怪,但我确认 await 有效果await this.setState( data: true , () => console.log('callback')); console.log('inline'); 明确表示 callback 将首先被解雇,但是 - 删除 await 并像通常 setState 一样工作 - 先是 inline 然后是 callback @DavinTryon 似乎是正确的,你应该把它变成一个答案,这对未来的其他人会有帮助,而且这是一个很常见的搜索 我也想详细说明一下。 setState 返回的 undefined 是异步的吗?组件更新后总是返回吗? 【参考方案1】:

rv 或 await 的返回值定义为:

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

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

【讨论】:

你会认为这会发生。但事实并非如此。 Await 暂停代码,直到 setState 完成......然后运行内联之后的任何内容。试试控制台日志 我同意。只需将警报附加到渲染函数中的警报状态,您就会看到。【参考方案2】:

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

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

如果你想用一个 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

【讨论】:

我不想改变任何东西,代码有效。在我的帖子下查看 Vlad 的评论,或使用您自己的控制台日志进行尝试。 await 之后的任何代码都将暂停并仅在 this.setState 完成更新后运行! 可能是因为马上更新了,不是这样就不行了。 我明白你在说什么,当 setState 决定等待时,我的方法可能不起作用。测试它可能是个好主意。但是 setState 仍然表现出奇怪的行为。我将在原文中添加另一个示例来演示这一点。 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 casesref @Gabriel 你的命名 setStateAsync 相当混乱...... setState 不是已经 async 了吗?将您的函数命名为 setStateSync 是否更好,因为这里的目的是让它看起来同步。【参考方案3】:

我还没有对此进行测试,但这是我认为正在发生的事情:

await 返回的undefinedsetState 回调之后排队。 await 在下面(在regenerator-runtime 中)执行Promise.resolve,这反过来又将控制权交给事件循环中的下一个项目。

所以,setState 回调恰好排在 await 之前是巧合。

您可以通过在 setState 周围放置 setTimeout(f => f, 0) 来测试它。

babel 中的regenerator-runtime 本质上是一个使用Promise.resolve 产生控制的循环。你可以看到_asyncToGenerator里面,它有一个Promise.resolve

【讨论】:

setTimeout 确实会破坏它,但这是意料之中的。在任何承诺周围设置一个 setTimeout ,它会破坏异步等待,不是吗?但是如果 setState 在所有其他情况下都像 async await 中的承诺一样,那么这仍然是一个可行的模式,不是吗? @LeoFabrikant setState 不像一个承诺。 await 刚刚在 setState 回调之后排队。只有一个事件循环。 我的意思是,在 setState 回调之前等待何时排队? 这取决于setState 回调相对于await 的返回如何/何时排队。但是使用 awaitregenerator 会生成大量从根本上改变异步流程的代码(但希望不会改变行为)。 我不完全理解,因为我还没有完全理解事件循环以及事件如何排队,但这似乎是合乎逻辑的解释【参考方案4】:

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


    await 放在 this.handleChange 前面,这将调度其余 changeAndValidate 函数的执行到仅在 await 解析其右侧指定的值时运行,在本例中为 this.handleChange 返回的值

    this.handleChange,在await的右边,执行:

    2.1。 setState 运行它的更新程序,但因为 setState 不保证立即更新,它可能会将更新安排在以后发生 (没关系如果它是立即的或在以后的时间点,重要的是它是预定的)

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

    2.3。 this.handleChange 然后退出返回 undefined (返回 undefined 因为函数返回 undefined,除非另有明确说明)

    await 然后接受这个 undefined 并且由于它不是一个承诺,它使用 Promise.resolve(undefined) 并等待它 - 它不是立即可用的,因为在幕后它被传递给它的异步 .then 方法:

“传递到 Promise 中的回调永远不会在 javascript 事件循环的当前运行完成”

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

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

    事件循环到达并拾取 undefined,其计算结果为 undefined (如果需要,我们可以存储它,因此= 常用于await前面存储解析结果)

    5.1。 Promise.resolve() 现在已经完成,这意味着 await 不再生效,所以可以继续执行其余的函数

    您的验证代码运行

【讨论】:

是否会可靠地将未定义的返回置于事件循环中的 setState 更新之后? 我不确定,这取决于使用的更新算法,找不到足够的信息,但是,如果算法批量更新并在当前事件周期结束时运行它们循环然后应该保证 所以这只是运气! :D【参考方案5】:

举个简单的例子:test-02中的代码await this.setState( a: 10 );其实等于test-03中的this.setState( a: 10 ); await undefined;。而await undefined; 会导致其后面的代码在下一个事件循环中执行。

【讨论】:

以上是关于为啥 Async Await 可以与 React setState 一起使用?的主要内容,如果未能解决你的问题,请参考以下文章

React Native Async\Await 无法与 setState 一起正常工作

为啥 try .. catch() 不能与 async/await 函数一起使用?

为啥 Java 没有 async/await?

[react] 在React中怎么使用async/await?

当循环在异步函数内部而不是相反时,为啥 Async/Await 可以正常工作?

如何在 React (async/await) 中创建一个原子进程?