Почему Async Await работает с React setState?

Я использую async ждут с babel в моем проекте ReactJS. Я обнаружил удобное использование с 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. Не красиво. Но в примере кода, как-то ждут, знает, что handleChange завершен после того, как государство обновилось... Но я думал, что ждут только работы с обещаниями и ждет обещания разрешить, прежде чем продолжить. Theres нет обещания и никакого разрешения в handleChange... Как он знает, что ждать?

По-видимому, подразумевается, что setState запускается асинхронно и ждет, как-то известно о завершении. Может быть, setState использует обещания внутри?

Версии:

реагировать: "^ 15.4.2"

babel-core: "^ 6.26.0"

babel-preset-env: "^ 1.6.0",

babel-preset-реагировать: "^ 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"

Ответ 1

Я попытался сделать все возможное, чтобы упростить и дополнить ответ Дэвина, чтобы вы могли лучше понять, что на самом деле происходит здесь:


  1. ОЖИДАНИЕ помещается перед this.handleChange, это будет запланировать выполнение остальных функций changeAndValidate работать только при Await решает значение, указанное справа от него, в этом случае значение, возвращаемое this.handleChange
  2. this.handleChange, справа от ожидания, выполняет:

    2.1. setState запускает свой updater, но поскольку setState не гарантирует немедленное обновление, потенциально планирует, что обновление произойдет позже (неважно, будет ли это немедленно или в более поздний момент времени, все, что имеет значение, это то, что он запланирован)

    2.2. console.log("синхронный код") работает...

    2,3. this.handleChange затем завершает возврат undefined (возвращает undefined, поскольку функции возвращают undefined, если явно не указано иное)

  3. ожидайте, тогда это не определено, и поскольку это не обещание, оно преобразует его в разрешенное обещание, используя Promise.resolve(undefined) и ждет его - оно не сразу доступно, потому что за кулисами оно переходит к его методу .then, который является асинхронным:

"Обратные вызовы, переданные в обещание, никогда не будут вызваны до завершения текущего цикла цикла JavaScript",

3.1. это означает, что undefined будет помещен в конец очереди событий (что означает, что теперь он находится за нашей установкой setState в очереди событий...)

  1. цикл событий, наконец, достигает и поднимает наше обновление SetState, которое теперь выполняется...

  2. цикл событий достигает и выбирает неопределенный, который оценивается как неопределенный (мы могли бы сохранить это, если бы захотели, следовательно, = обычно используется перед ожиданием сохранения разрешенного результата)

    5.1. Promise.resolve() теперь закончен, что означает, что ожидания больше не влияют, поэтому остальная часть функции может возобновиться

  3. ваш код проверки выполняется

Ответ 2

Я еще не тестировал это, но вот что я думаю, что происходит:

undefined возвращаемый await в очереди после setState обратного вызова. await делает Promise.resolve снизу (в regenerator-runtime), который в свою очередь, дает контроль к следующему пункту цикла событий.

Таким образом, совпадение заключается в том, что setState вызов setState оказывается в очереди впереди await.

Вы можете проверить это, поставив setTimeout (f => f, 0) вокруг setState.

regenerator-runtime в babel - это, по сути, цикл, который использует Promise.resolve для управления. Вы можете видеть внутри _asyncToGenerator, у него есть Promise.resolve.

Ответ 3

Значение rv или возвращаемое значение ожидания определяется как:

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

Так как handleChange не является асинхронным или обещающим значением, он просто возвращает натуральное значение (в этом случае возврат невозможен, поэтому undefined). Таким образом, здесь нет триггера цикла асинхронного события, чтобы "сообщить ему, что handleChange сделан", он просто запускается в том порядке, который вы ему дали.

Ответ 4

setState() не всегда сразу обновляет компонент doc

Но это может быть и здесь.

Если вы хотите заменить обратный вызов обещанием, вы можете реализовать его самостоятельно:

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

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

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