【问题标题】:Async Await not working correctly with useStateCallback异步等待无法与 useStateCallback 一起正常工作
【发布时间】:2020-12-14 10:21:33
【问题描述】:

我需要更新序列中的相同状态:

// lets say this is the state
const [someState, setSomeState] = useState({a: 1, b: 1})

// and i need to update it like so
// from an external function     
setSomeState({a: 2})
// wait until the first one is actually updates
// and do the next one  
setSomeState({b: 2})
// wait until the second one is actually updates
// "tell" the parent function that both are updated

现在我这样做:

// i use a customized hook useStateCallback
function useStateCallback(initialState) {
  const [state, setState] = useReducer((state, newState) => ({ ...state, ...newState }), initialState)
  const cbRef = useRef(null)

  const setStateCallback = (state, cb) => {
    cbRef.current = cb
    setState(state)
  }

  useEffect(() => {
    if (cbRef.current) {
      cbRef.current(state)
      cbRef.current = null
    }
  }, [state])

  return [state, setStateCallback]
}

// and execute it like so
// from outside of a component
export const someFunction = async setState => {
    await setState({ a: 2 }, () => Promise.resolve())
    await setState({ b: 2 }, () => Promise.resolve())
}

但是这个回调不能正常工作,调用函数实际上并没有等待状态变化。

所以我的问题是:

  1. useStateCallback 挂钩如何与 Promise.resolve() 结合使用?
  2. Promise.resolve() 在这种特殊情况下如何工作?
  3. 如何一个接一个地更新相同的状态,只有在第一个更新(使用挂钩)时才会更新第二个状态,并“告诉”父函数他们都完成了更新?

【问题讨论】:

  • 我们需要更多关于setState() 是什么的信息。它返回什么?它接受哪些论据?

标签: javascript reactjs async-await es6-promise


【解决方案1】:

您的设置的问题是 setStateCallback 设置回调 ref 然后返回。然后从 useEffect 调用该回调。这里的问题是,当 useEffect 被调用时,原来的调用函数已经完成了。

如果我们只是想确保等待 setState 完成,那么我们可以通过在 setStateCallback 中添加 useEffect 并将其包装在 Promise 中来解决这个问题。这基本上会说,只有在 useEffect 完成时才解决。否则就等着

  const setStateCallback = (state, cb) => {
    return new Promise((resolve) => {
       cbRef.current = cb
       setState(state)
       useEffect(async () => {
          if (cbRef.current) {
            await cbRef.current(state) // Calls your wrap callback promise here in case there's anything you want to do. Otherwise you don't need that wrapper callback anymore and we can remove this await.
            cbRef.current = null
          }
          resolve();  --> This will basically resolve once useEffect is done calling the functions it needs to call. 
       }, [state])
  });

【讨论】:

    【解决方案2】:

    您的setStateCallback 不返回Promise,但它接受回调;这就是为什么不能用await 语法直接调用它的原因。你需要的只是一个简单的包装器来promisify它。

    类似:

    function useStateCallback(initialState) {
      const [state, setState] = useReducer((state, newState) => ({ ...state,
        ...newState
      }), initialState)
      const cbRef = useRef(null)
    
      const setStateCallback = (state, cb) => {
        cbRef.current = cb
        setState(state)
      }
    
      useEffect(() => {
        if (cbRef.current) {
          cbRef.current(state)
          cbRef.current = null
        }
      }, [state])
    
      const setStateAsync = state => new Promise((resolve, reject) => setStateCallback(state, resolve))
    
      return [state, setStateCallback, setStateAsync]
    }
    

    现在你可以将你的状态初始化为:

    const [state, setStateCallback, setStateAsync] = useStateCallback({})
    

    从现在开始,您可以使用async 表单setStateAsync。这混合到一个固定的clear 函数(这次它接受一个async 函数):

    export const clear = async setState => {
        await setState({ isWritable: false })
        await setState({ isVisible: false })
    }
    

    应该让你使用:

    await clear(setStateAsync);
    

    具有预期的async 行为。

    【讨论】:

    • Your setStateCallback does not returns a Promise, but it accept a callback; that's why it can't be directly called with the await syntax 但为什么它要重新调整一个承诺,我可以在控制台中看到它甚至获得它的价值(如果我通过回调传递它)?我实际上想了解它是如何工作的以及我错过了什么
    • OP 编写包装器的方式已经返回了一个承诺。所以不需要在它上面再做一次包装。我同意我们可以在 useStateCallback 中返回一个承诺,这样我们就不必每次都创建一个包装器。我认为实际问题是另一回事。这是他的包装供参考。 await setState({ a: 2 }, () => Promise.resolve()) 它基本上调用 setState 并给它一个回调,返回一个承诺。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-16
    • 2019-11-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多