#javascript #reactjs #async-await #es6-promise
#javascript #reactjs #async-await #es6-обещание
Вопрос:
Мне нужно обновить то же состояние в последовательности:
// 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())
}
но этот обратный вызов выполняется правильно, и вызывающая функция фактически не ожидает изменений состояния.
Итак, мои вопросы:
- Как перехват useStateCallback работает в сочетании с
Promise.resolve()
? - Как
Promise.resolve()
работает в этом конкретном случае? - Как мне обновлять одно и то же состояние одно за другим, когда второе будет обновляться только при обновлении первого (с помощью перехватов) и «сообщать» родительской функции, что они оба завершили свои обновления?
Комментарии:
1. Нам нужно больше информации о том, что
setState()
такое. Что это возвращает? Какие аргументы он принимает?
Ответ №1:
Проблема с вашей настройкой заключается в том, что setStateCallback устанавливает ссылку обратного вызова, а затем возвращает. И затем вы вызываете этот обратный вызов из useEffect. Проблема здесь в том, что к моменту вызова useEffect исходная функция вызова уже завершена.
Если мы просто хотим убедиться, что дождемся завершения setState, то мы бы решили это как таковое, добавив useEffect в setStateCallback и обернув его в обещание. В основном это будет означать, разрешится только в том случае, если 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
синтаксиса. Что вам нужно, так это просто оболочка, чтобы обещать это.
Что-то вроде:
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
поведением.
Комментарии:
1.
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
но почему он перенастраивает обещание, которое я могу видеть в консоли и даже получить его значение (если я передаю его через обратный вызов)? Я действительно хочу понять, как это работает и чего мне не хватает2. Способ, которым OP написал свою оболочку, уже возвращает обещание. Поэтому нет необходимости выполнять еще один перенос поверх него. Я согласен, что мы могли бы возвращать обещание в useStateCallback, чтобы нам не приходилось каждый раз создавать оболочку. Я думаю, что реальная проблема заключается в чем-то другом. Вот его оболочка для справки.
await setState({ a: 2 }, () => Promise.resolve())
Он в основном вызывает setState и выдает ему обратный вызов, который возвращает обещание.