Visualisation des promesses et Async / Await



Bonjour mes amis!

Je vous présente la traduction de l'article "JavaScript Visualized: Promises & Async / Await" de Lydia Hallie.

Avez-vous rencontré du code JavaScript qui ... ne fonctionne pas comme prévu? Lorsque les fonctions sont exécutées dans un ordre arbitraire et imprévisible, ou sont exécutées avec un retard. L'une des tùches principales des promesses est de rationaliser l'exécution des fonctions.

Ma curiositĂ© insatiable et mes nuits blanches ont portĂ© leurs fruits - grĂące Ă  eux, j'ai crĂ©Ă© plusieurs animations. Il est temps de parler des promesses: comment elles fonctionnent, pourquoi elles devraient ĂȘtre utilisĂ©es et comment elles sont rĂ©alisĂ©es.

introduction


Lors de l'écriture de code JS, nous devons souvent traiter des tùches qui dépendent d'autres tùches. Supposons que nous voulons obtenir une image, la compresser, lui appliquer un filtre et l'enregistrer.

Tout d'abord, nous devons obtenir une image. Pour ce faire, utilisez la fonction getImage. AprÚs avoir chargé l'image, nous la transmettons à la fonction resizeImage. AprÚs avoir compressé l'image, nous lui appliquons un filtre à l'aide de la fonction applyFilter. AprÚs compression et application du filtre, nous enregistrons l'image et informons l'utilisateur du succÚs.

En conséquence, nous obtenons les éléments suivants:



Hmm ... Avez-vous remarqué quelque chose? Malgré le fait que tout fonctionne, cela ne semble pas le meilleur. Nous obtenons de nombreuses fonctions de rappel imbriquées qui dépendent des rappels précédents. C'est ce qu'on appelle l'enfer de rappel et cela rend trÚs difficile la lecture et la maintenance du code.

Heureusement, nous avons aujourd'hui des promesses.



Syntaxe de promesse


Des promesses ont été introduites dans ES6. Dans de nombreux manuels, vous pouvez lire ce qui suit:

Une promesse (promesse) est une valeur qui est remplie ou rejetée à l'avenir.

Ouais ... Explication moyenne. À un moment donnĂ©, cela m'a fait considĂ©rer les promesses comme quelque chose d'Ă©trange, de vague, une sorte de magie. Que sont-ils vraiment?

Nous pouvons créer une promesse avec un constructeur Promisequi prend une fonction de rappel comme argument. Cool, essayons:



Attendez, qu'est-ce qui revient ici?

PromiseEst un objet qui contient status ( [[PromiseStatus]]) et value ( [[PromiseValue]]). Dans l'exemple ci-dessus, la valeur [[PromiseStatus]]est pendinget la valeur de la promesse est undefined.

Ne vous inquiĂ©tez pas, vous n'avez pas besoin d'interagir avec cet objet, vous ne pouvez mĂȘme pas accĂ©der aux propriĂ©tĂ©s [[PromiseStatus]]et [[PromiseValue]]. Cependant, ces propriĂ©tĂ©s sont trĂšs importantes lorsque vous travaillez avec des promesses.

PromiseStatus ou le statut d'une promesse peut prendre l'une des trois valeurs suivantes:

  • fulfilled: resolved (). ,
  • rejected: rejected (). -
  • pending: , pending (, )

Cela semble génial, mais quand une promesse obtient-elle les statuts indiqués? Et pourquoi le statut est-il important?

Dans l'exemple ci-dessus, nous transmettons une Promisesimple fonction de rappel au constructeur () => {}. Cette fonction prend en fait deux arguments. La valeur du premier argument, généralement appelée resolveou res, est la méthode invoquée lors de l'exécution de la promesse. La valeur du deuxiÚme argument, généralement appelée rejectou rej, est la méthode appelée lorsque la promesse est rejetée en cas de problÚme.



Voyons ce qui est sorti sur la console lors de l'appel des méthodes resolveet reject:



Cool! Maintenant, nous savons comment nous débarrasser du statut pendinget du sens undefined. Le statut de la promesse lors de l'appel de la méthode resolveest fulfilled, lorsque reject-rejected.

[[PromiseValue]]ou la valeur de la promesse est la valeur que nous transmettons aux méthodes resolveou rejectcomme argument.

Fait amusant: Jake Archibald aprÚs avoir lu cet article a signalé un bogue dans Chrome, qui est plutÎt fulfilledrevenu resolved.



Ok, maintenant nous savons comment travailler avec l'objet Promise. Mais Ă  quoi sert-il?

Dans l'introduction, j'ai donné un exemple dans lequel nous obtenons une image, la compressons, lui appliquons un filtre et l'enregistrons. Ensuite, tout s'est terminé par un enfer de rappel.

Heureusement, les promesses aident à y faire face. Nous réécrivons le code afin que chaque fonction renvoie une promesse.

Si l'image est chargée, nous réalisons une promesse. Sinon, si une erreur se produit, rejetez la promesse:



Voyons ce qui se passe lorsque ce code est exécuté dans le terminal:



Cool! Promis revient avec des données analysées («analysées»), comme prévu.

Mais ... quelle est la prochaine étape? Nous ne nous intéressons pas au sujet de promis, nous nous intéressons à ses données. Il existe 3 méthodes intégrées pour obtenir une valeur Promise:

  • .then(): appelĂ© aprĂšs avoir exĂ©cutĂ© une promesse
  • .catch(): appelĂ© aprĂšs le rejet de la promesse
  • .finally(): toujours appelĂ©, Ă  la fois aprĂšs l'exĂ©cution et aprĂšs le rejet d'une promesse



La méthode .thenprend la valeur transmise à la méthode resolve:



La méthode .catchprend la valeur transmise à la méthode reject:



Enfin, nous avons obtenu la valeur souhaitée. Nous pouvons tout faire avec cette valeur.

Lorsque nous sommes confiants dans l'accomplissement ou le rejet d'une promesse, vous pouvez écrire Promise.resolvesoit Promise.rejectavec la valeur appropriée.



Il s'agit de la syntaxe qui sera utilisée dans les exemples suivants.



Le résultat .thenest la valeur de la promesse (c'est-à-dire que cette méthode renvoie également la promesse). Cela signifie que nous pouvons en utiliser autant .thenque nécessaire: le résultat du précédent .thenest passé en argument au suivant .then.



Dans getImagenous pouvons en utiliser plusieurs .thenpour transférer l'image traitée vers la fonction suivante.



Cette syntaxe est bien meilleure que l'échelle des fonctions de rappel imbriquées.



MicrotĂąches et tĂąches (macro)


Eh bien, maintenant nous savons comment créer des promesses et comment en extraire des valeurs. Ajoutez du code à notre script et réexécutez-le: tout d'



abord, il s'affiche dans la console Start!. C'est normal, car nous avons la premiÚre ligne de code console.log('Start!'). La deuxiÚme valeur affichée sur la console n'est End!pas la valeur de la promesse remplie. La valeur de la promesse est affichée en dernier. Pourquoi est-ce arrivé?

Nous voyons ici la puissance des promesses. Bien que JS soit monothread, nous pouvons rendre le code asynchrone avec Promise.

OĂč d'autre pourrions-nous observer un comportement asynchrone? Certaines mĂ©thodes intĂ©grĂ©es au navigateur, telles que setTimeout, peuvent simuler l'asynchronie.

Droite Dans la boucle d'événement (boucle d'événement), il existe deux types de files d'attente: la file d'attente des tùches (macro) ou simplement des tùches (file d'attente des tùches (macro), file d'attente des tùches) et la file d'attente des microtùches ou simplement des microtùches (file d'attente des microtùches, microtùches).

Qu'est-ce qui s'applique Ă  chacun d'eux? Bref, alors:

  • TĂąches de macro: setTimeout, setInterval, setImmediate
  • MicrotĂąches: process.nextTick, Promise callback, queueMicrotask

Nous voyons Promisedans la liste des microtùches. Lorsque la Promiseméthode est exécutée et appelée then(), catch()ou finally(), la fonction de rappel avec la méthode est ajoutée à la file d'attente des microtùches. Cela signifie que le rappel avec la méthode n'est pas exécuté immédiatement, ce qui rend le code JS asynchrone.

Quand est la méthode then(), catch()ou finally()est-elle exécutée? Les tùches de la boucle d'événements ont la priorité suivante:

  1. Tout d'abord, les fonctions de la pile d'appels sont exécutées. Les valeurs renvoyées par ces fonctions sont supprimées de la pile.
  2. Une fois la pile libérée, les microtùches sont placées et exécutées l'une aprÚs l'autre (les microtùches peuvent renvoyer d'autres microtùches, créant un cycle sans fin de microtùches).
  3. Une fois la pile et la file d'attente de microtùches libérées, la boucle d'événements recherche les macros. Les tùches de macro sont insérées dans la pile, exécutées et supprimées.



Prenons un exemple:

  • Task1: Une fonction qui est ajoutĂ©e Ă  la pile immĂ©diatement, par exemple, par un appel en code.
  • Task2, Task3, Task4: Exemple Mikrozadachi thenProMIS ou tĂąche ajoutĂ©e par queueMicrotask.
  • Task5, Task6: tĂąches de macro, par exemple, setTimeoutousetImmediate



Task1Retourne d' abord une valeur et est supprimé de la pile. Ensuite, le moteur recherche les microtùches dans la file d'attente correspondante. AprÚs avoir ajouté et supprimé des microtùches de la pile, le moteur recherche les tùches de macro, qui sont également ajoutées à la pile et supprimées de celle-ci aprÚs avoir renvoyé les valeurs.

Assez de mots. Écrivons le code.



Dans ce code, nous avons une macro setTimeouttùche et une micro tùche .then. Exécutez le code et voyez ce qui s'affiche dans la console.

Remarque: dans l'exemple ci-dessus, j'utilise des méthodes telles que console.log, setTimeoutet Promise.resolve. Toutes ces méthodes sont internes, elles n'apparaissent donc pas dans la trace de la pile - ne soyez pas surpris lorsque vous ne les trouvez pas dans les outils de dépannage du navigateur.

Dans la premiÚre ligne, nous avonsconsole.log. Il est ajouté à la pile et affiché dans la console Start!. AprÚs cela, cette méthode est supprimée de la pile et le moteur continue d'analyser le code.



Le moteur atteint setTimeout, qui est ajouté à la pile. Cette méthode est une méthode de navigateur intégrée: sa fonction de rappel ( () => console.log('In timeout')) est ajoutée à l'API Web et est là avant le déclenchement du minuteur. Malgré le fait que le compteur de la minuterie est à 0, le rappel est toujours placé d'abord dans WebAPI puis dans la file d'attente des tùches de macro: setTimeout- il s'agit d'une tùche de macro.



Ensuite, le moteur atteint la méthode Promise.resolve(). Cette méthode est ajoutée à la pile, puis exécutée avec une valeur Promise. Son rappel thenest placé dans la file d'attente des microtùches.



Enfin, le moteur atteint la deuxiÚme méthode console.log(). Il est immédiatement poussé sur la pile, il est sorti sur la consoleEnd!, la méthode est supprimée de la pile et le moteur continue.



Le moteur «voit» que la pile est vide. Vérification de la file d'attente des tùches. Il y est situé then. Il est poussé sur la pile, la valeur de la promesse est affichée sur la console: dans ce cas, une chaßne Promise!.



Le moteur voit que la pile est vide. Il "regarde" dans la file d'attente des microtĂąches. Elle est vide aussi.

Il est temps de vérifier la file d'attente des tùches macro: elle est là setTimeout. Il est poussé sur la pile et renvoie une méthode console.log(). Une chaßne est sortie sur la console 'In timeout!'. setTimeoutest retiré de la pile.



Terminé. Maintenant, tout s'est mis en place, non?



Async / attente


ES7 a introduit une nouvelle façon de travailler avec du code asynchrone dans JS. En utilisant les mots clés asyncet awaitnous pouvons créer une fonction asynchrone qui renvoie implicitement une promesse. Mais ... comment on fait ça?

Plus tÎt, nous avons expliqué comment créer explicitement un objet Promise: en utilisant new Promise(() => {}), Promise.resolveou Promise.reject.

Au lieu de cela, nous pouvons créer une fonction asynchrone qui renvoie implicitement l'objet spécifié. Cela signifie que nous n'avons plus besoin de créer manuellement Promise.



Le fait qu'une fonction asynchrone renvoie implicitement une promesse est, bien sûr, génial, mais la puissance de cette fonction se manifeste pleinement lors de l'utilisation d'un mot-clé await.awaitfait attendre la fonction asynchrone que la promesse (sa valeur) se termine. Pour obtenir la valeur de la promesse réalisée, nous devons affecter à la variable la valeur attendue (attendue) de la promesse.

Il s'avÚre que nous pouvons retarder l'exécution d'une fonction asynchrone? Génial, mais ... qu'est-ce que cela signifie?

Voyons ce qui se passe lorsque vous exécutez le code suivant:







Au début, le moteur voit console.log. Cette méthode est poussée sur la pile et affichée sur la console Before function!.



Ensuite, la fonction asynchrone est appelée myFunc(), son code est exécuté. Dans la premiÚre ligne de ce code, nous appelons la seconde console.logavec une ligne 'In function!'. Cette méthode est ajoutée à la pile, sa valeur s'affiche sur la console et elle est supprimée de la pile.



Le code de fonction est ensuite exécuté. Sur la deuxiÚme ligne, nous avons un mot-clé await.

La premiÚre chose qui se passe ici est l'exécution de la valeur attendue: dans ce cas, la fonction one. Il est poussé sur la pile et renvoie une promesse. Une fois la promesse terminée et la fonction oneretournée la valeur, le moteur voit await.

AprÚs cela, l'exécution de la fonction asynchrone est retardée. L'exécution du corps de la fonction est suspendue, le code restant est exécuté comme une microtùche.



AprÚs que l'exécution de la fonction asynchrone a été retardée, le moteur revient à l'exécution du code dans un contexte global.



Une fois que tout le code a été exécuté dans un contexte global, la boucle d'événements recherche les microtùches et les détecte myFunc(). myFunc()poussé sur la pile et exécuté.

La variable resobtient la valeur de la promesse exécutée retournée par la fonction one. Nous appelons console.logavec la valeur de la variable res: une chaßne One!dans ce cas. One!Il s'affiche dans la console.

Terminé. Remarquez la différence entre la fonction asynchrone et la méthode thenpromis? Mot-cléawaitretarde l'exécution d'une fonction asynchrone. Si nous thenl'utilisions, le corps Promise continuerait de fonctionner.



Il s'est avéré assez verbeux. Ne vous inquiétez pas si vous ne vous sentez pas en sécurité lorsque vous travaillez avec des promesses. Il faut du temps pour s'y habituer. Ceci est commun à toutes les techniques de travail avec du code asynchrone dans JS.

Voir Ă©galement "Visualisation du travail des travailleurs de service" .

Merci pour votre temps. J'espÚre que cela a été bien dépensé.

All Articles