web-dev-qa-db-fra.com

Pourquoi Async Await fonctionne-t-il avec React setState?

J'ai utilisé async attendre avec Babel dans mon projet ReactJS. J'ai découvert un usage pratique avec React setState que je voudrais simplement mieux comprendre. Considérons ce code:

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')    
}

Mon intention était que le code de validation asynchrone soit exécuté après la mise à jour du composant. Et il fonctionne! Le journal de la console résultant montre:

synchronous code
updated component
asynchronous validation code

Le code de validation ne sera exécuté qu'une fois que handleChange aura mis à jour l'état et que le nouvel état sera rendu.

Habituellement, pour exécuter le code après la mise à jour de l'état, vous devez utiliser un rappel après this.setState. Ce qui signifie que si vous voulez exécuter quoi que ce soit après handleChange, vous devez lui attribuer un paramètre de rappel qui est ensuite transmis à setState. Pas beau. Mais dans l'exemple de code, wait sait en quelque sorte que handleChange est terminé après la mise à jour de l'état ... Mais je pensais que wait ne fonctionnait qu'avec des promesses et attendait une promesse à résoudre avant de continuer. Il n'y a pas de promesse ni de résolution dans le changement ... Comment sait-on quoi attendre?

L'implication semble être que setState est exécuté de manière asynchrone et attend en quelque sorte au courant de son exécution. Peut-être que setState utilise les promesses en interne?

Versions:

réagir: "^ 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"

15
Leo Fabrikant

J'ai essayé de faire de mon mieux pour simplifier et compléter la réponse de Davin afin que vous puissiez avoir une meilleure idée de ce qui se passe réellement ici:


  1. wait est placé devant this.handleChange , cela va programmer l'exécution du reste de changeAndValidate function à exécuter uniquement lorsque wait est résolu la valeur spécifiée à droite de celle-ci, dans ce cas la valeur renvoyée par this.handleChange
  2. this.handleChange , à droite de wait , exécute:

    2.1. setState exécute son programme de mise à jour, mais parce que setState ne garantit pas une mise à jour immédiate, il est possible que la mise à jour soit programmée ultérieurement(peu importe si c'est immédiat ou ultérieur. avec le temps, tout ce qui compte, c’est que c’est prévu)

    2.2. console.log ('code synchrone') s'exécute ...

    2.3. this.handleChange quitte ensuite en retournant undefined (retourne undefined car les fonctions retournent undefined sauf spécification contraire explicite)

  3. wait prend alors ce undefined et comme ce n’est pas une promesse, il la convertit en promesse résolue en utilisant Promise.resolve (undefined) et l’attend - il n’est pas immédiatement disponible car derrière le scènes il est passé à sa méthode .then qui est asynchrone:

«Les rappels passés en promesse ne seront jamais appelés avant le achèvement de l'exécution en cours de la boucle d'événement JavaScript ”

3.1. cela signifie que undefined sera placé à l’arrière de la file d’événements, (c’est-à-dire qu’il se trouve maintenant derrière notre programme de mise à jour setState dans la file d’événements…)

  1. boucle d'événement atteint finalement et récupère notre setState update, qui s'exécute maintenant ... 

  2. boucle d'événement atteint et récupère undefined , qui correspond à undefined (nous pourrions le stocker si nous le souhaitions, d'où le = généralement utilisé devant attendre pour stocker le résultat résolu)

    5.1. Promise.resolve () est maintenant terminé, ce qui signifie wait n'est plus en vigueur, le reste de la fonction peut donc reprendre

  3. votre validation code fonctionne
18
linasmnew

Je n'ai pas encore testé cela, mais voici ce que je pense qui se passe:

La undefined renvoyée par await est mise en file d'attente après le rappel setState. La await exécute un Promise.resolve sous (dans le regenerator-runtime) qui, à son tour, permet de contrôler l'élément suivant de la boucle d'événements.

C'est donc un hasard si le callback setState est mis en file d'attente avant le await.

Vous pouvez le tester en mettant un setTimeout (f => f, 0) autour de setState.

regenerator-runtime dans babel est essentiellement une boucle qui utilise Promise.resolve pour donner le contrôle. Vous pouvez voir l’intérieur du _asyncToGenerator , il a un Promise.resolve.

3
Davin Tryon

La rv ou la valeur de retour de wait est définie comme suit:

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

Ainsi, puisque handleChange n'est pas une valeur asynchrone ou promise, il renvoie simplement la valeur naturelle (dans ce cas, il n'y a pas de retour, donc undefined). Ainsi, il n’ya pas de déclencheur de boucle d’événement asynchrone ici pour "lui faire savoir que le changement est terminé", il est simplement exécuté dans l’ordre que vous lui avez donné.

1
Sterling Archer

setState() ne met pas toujours immédiatement à jour le composant doc

Mais c'est peut-être le cas ici.

Si vous souhaitez remplacer le rappel par une promesse, vous pouvez l'implémenter vous-même:

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

0
Gabriel Bleu