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

Я использую async await с 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 и нового состояния оказанный.

обычно для запуска кода после обновления состояния вам придется использовать обратный вызов после этого.выполнении функция setState. Это означает, что если вы хотите запустить что-либо после handleChange, вы должны дать ему параметр обратного вызова, который затем передается setState. Не очень. Но в примере кода каким-то образом await знает, что handleChange завершен после обновления состояния... Но я думал, что ожидание работает только с обещаниями и ждет обещания решить, прежде чем продолжить. Theres не обещаю и никакого разрешения в handleChange... Откуда он знает, чего ждать??

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

варианты:

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

Бабель-ядро: "^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-декораторы-наследие: "^1.3.4",

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

4 ответов


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


  1. ждут стоит перед этого.handleChange, это расписание исполнение остаток changeAndValidate функция только для запуска, когда ждут разрешает значение, указанное справа от него, в этом случае возвращаемое значение by этого.handleChange
  2. этого.handleChange, справа от ждут осуществляет:

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

    2.2. приставка.выполняется журнал ('синхронный код')...

    2.3. этого.handleChange тогда выходит возвращение undefined (возвращает undefined, потому что функции возвращают значение undefined, если явно не указано иное)

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

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

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

  1. цикл обработки событий наконец-то достигает и поднимает наш выполнении функция setState обновление, которое сейчас выполняет...

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

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

  3. код проверка код

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

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

Итак, это совпадение, что setState обратный вызов оказывается в очереди перед await.

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

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


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

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

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


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