Faire le quiz link W3C close

Les promesses en JavaScript.

#1 Pré-requis open

#2 Avantage d'une promesse open

  1. Evite l’enfer des callbacks (callback hell). On parle de callback hell, lorsque dans une opération asynchrone, on a besoin de faire une deuxième opération asynchrone, et que dans cette deuxième, on doit en faire une troisième, et ça peut continuer encore et encore... Dans ce cas, si on n'utilise pas les promesses, le code JavaScript va manquer de lisibilité. Vous avez un exemple ci-dessous. Un exemple, où il y a peu de code dans le corps des callbacks. S'il y avait plus de code dans le corps des fonctions callback, on aurait encore moins de lisibilité.
    Exemple de callback hell
  2. Avec les promesses, vous avez la possibilité de chaîner les opérations asynchrones. Ca permet de les écrire les unes en dessous des autres, et de les exécuter les unes après les autres. Comprenez bien, que c'est dans le traitement de la réussite d'une opération asynchrone, que la suivante est lancée. Ca résout le manque de lisibilité que l'on vient de voir.

    Vous allez avoir du code qui va ressembler au pseudo code ci-dessous et qui sera très lisible. C'est ça l'avantage.

    Exemple de code avec promesse

#3 Objectif du tuto open

Lorsque vous avez besoin de faire des opérations asynchrones, vous pouvez vous retrouver devant deux situations différentes :

  1. Vous travaillez avec une Api qui utilise déjà des promesses pour ses opérations asynchrones. Dans ce cas, vous êtes de "simples" utilisateurs de promesses. On dit que vous consommez des promesses. C'est le cas avec l'Api Fetch ou l'Api asynchrone de Node.js par exemple.
  2. Vous travaillez avec une Api qui n'utilise pas les promesses. C'est le cas des requêtes Ajax qui sont faites avec l'interface XMLHttpRequest. Dans ce cas, vous pouvez placer un objet Promise devant l'objet XMLHttpRequest de manière à gérer votre requête Ajax à travers une promesse. Dans ce cas, on dit que l'on promessifie l'opération asynchrone et là, on est obligé de descendre dans le fonctionnement interne de la promesse, car c'est un peu plus compliqué.

L'objectif de ce tuto, c'est couvrir les deux situations. Sachant que si vous savez promessifier, vous savez forcément utiliser une promesse.

Différentes situations

#4 Qu'est-ce qu'une promesse open

Une promesse c'est un objet JavaScript qui va nous permettre de connaître l'état d'une opération asynchrone et de la gérer. Il y a une phrase dans le MDN qui est intéressante, je vous la cite : Ainsi (en utilisant les promesses), les méthodes asynchrones renvoient des valeurs comme les méthodes synchrones.

En effet lorsque vous faites une opération synchrone, par exemple un appel système pour ouvrir un fichier, un open, eh bien, vous obtenez un résultat et un code de retour. Vous savez si ça a marché ou pas. Et vous le savez en une seule fois, puisque vous êtes bloqué en attente du résultat de cette opération.

Code de retour d'une opération synchrone

Avec une opération asynchrone, ce n'est pas la même problématique. Vous commencez par faire une demande. A ce stade, une propriété de l'objet promesse va noter cette situation et s'attribuer une certaine valeur. Ce sera la valeur pending : "Je suis en attente". Puis l'opération va réussir ou échouer et là, c'est l'OS qui va vous renvoyer des valeurs. Donc la propriété de la promesse va prendre d'autres valeurs. Ce sera fulfilled si l'opération est réussie ou rejected si l'opération a échoué. Ici ce qu'il faut remarquer c'est qu'il n'y a pas qu'une seule valeur de retournée dans l'opération d'entrée / sortie et qu'en plus ces valeurs évoluent dans le temps. Et ces valeurs sont stockées dans une propriété de l'objet promesse.

En plus, l'objet promesse va nous permettre d'attacher des traitements à ces valeurs renvoyées et il va les déclencher le moment venu. On peut voir l'objet promesse comme une sorte de super gestionnaire d'événements qui va gérer tous les événements liés à une opération asynchrone.

Enfin dernier point capital. On va pouvoir chaîner les objets promesses et retrouver l'avantage dont on a parlé plus haut.

Evolution des codes de retour d'une opération asynchrone

#5 Le constructeur Promise open

Pour construire une promesse, il faut construire un objet à l'aide du constructeur Promise. On fait un essai tel quel avec un new Promise. On voit que ça pose un problème, car il faut passer une fonction en argument du constructeur. Une fonction que l'on appelle l'exécuteur.

Donc, je refais ce new en passant une fonction vide, ça va me permettre de visualiser l'objet.

Il y a quatre choses qui nous intéressent à ce stade :

  1. Il y a deux propriétés internes qui sont entre doubles crochets. La première, c'est [[PromiseState]]. C'est elle qui va prendre différentes valeurs selon l'évolution de l'opération asynchrone.
  2. Ensuite, on a [[PromiseResult]]. Ca c'est le résultat au sens large de notre opération asynchrone. Ce sera un résultat si l'opération a réussi, ou ce sera la raison de l'échec si l'opération a échoué.
  3. Ensuite, il y a deux méthodes. La méthode then, qui sera appelée par la promesse, lorsque l'opération réussira. On passera en argument du then, la déclaration d'un callback, qui dira ce qu'il faut faire dans cette situation de réussite.
  4. La méthode catch, qui sera appelée par la promesse, lorsque l'opération échouera. On passera en argument du catch, la déclaration d'un callback, qui dira ce qu'il faut faire dans cette situation d'échec.
  5. Vous voyez que l'on fait exactement ce que l'on fait dans un gestionnaire d'événement, sauf qu'ici, il y a deux événements.
Qu'est ce qu'on trouve dans un objet constructeur Promise

#6 Les différentes valeurs de l'état d'une promesse open

La valeur de [[PromiseState]] correspond à l'état de l'opération asynchrone. Les différentes valeurs sont les suivantes :

  1. pending pour une opération qui a été initiée. On dit que la promesse est en attente.
  2. fulfilled pour une opération qui est terminée et qui s'est terminée par le succès de l'opération. On dit que la promesse est tenue. Et dans [[PromiseResult]], on trouve le résultat de l'opération.
  3. rejected pour une opération qui est terminée et qui s'est terminée par un échec. On dit que la promesse est rompue. Et dans [[PromiseResult]], on trouve la raison de cet échec.
  4. settled qui veut dire soit fulfilled, soit rejected. Ce n'est pas réellement une valeur, mais une facilité de langage pour dire l'un ou l'autre. Vous ne la verrez pas affichée. On dit que la promesse est acquittée.

#7 L'exécuteur open

Pour créer une promesse, on utilise le constructeur Promise et on lui passe en argument une fonction que l'on appelle l'exécuteur. L'écriture de cette fonction est à la charge du programmeur qui utilise la promesse et il y a des contraintes. Tout d'abord, cet exécuteur va lui-même prendre en argument deux callbacks resolve et reject. Dans l'exécuteur il faudra appeler resolve ou reject dans deux situations précises.

  • Lorsque l'opération réussira, il faudra appeler le callback resolve. L'état de la promesse passera de pending à fulfilled. Il faudra passer le résultat de l'opération en argument de resolve.
  • Lorsque l'opération échouera, il faudra appeler le callback reject. L'état de la promesse passera de pending à rejected. Il faudra passer la raison de l'échec en argument de reject.

Ces arguments sont stockés dans la variable interne [[PromiseResult]] de l'objet Promise.

Passer d'un état à l'autre dans une promesse

Ci-dessous, vous allez voir basculer la valeur de l'état de la promesse au bout de 5 secondes. La valeur va basculer de pending à fulfilled. Ensuite, je vais mettre en commentaire resolve pour appeler reject et la valeur va passer de pending à rejected.

Tester le code
Ci-dessous, j'ai appelé resolve avec la chaîne "Résultat de l'opération" en argument.
La promesse passe de l'état pending à l'état fullfilled.
Promesse dans l'état fulfilled
Tester le code
Ci-dessous, j'ai appelé reject avec la chaîne "Raison de l'échec" en argument.
La promesse passe de l'état pending à l'état rejected.
Promesse dans l'état rejected

#8 Déclarer les callbacks et les attacher open

Ci-dessous, on reprend l'exemple précédent. Maintenant, je déclare les callback et je les attache à un événement. Dans le then, on doit déclarer le callback resolve. Dans le catch, on doit déclarer le callback reject. Donc, on se retrouve forcément avec un paramètre d'entrée pour les déclarations des deux callbacks, puisque l'on a un argument d'appel pour les deux.

Tester le code
Appel de la méthode then
Tester le code
Appel de la méthode catch

#9 Promessifier une requête Ajax open

Je vais écrire une nouvelle fonction pour promessifier une requête Ajax asynchrone.

  1. Je récupère le code de base d'une requête Ajax asynchrone écrit avec XMLHttpRequest et je le place dans le corps de la fonction exécuteur de la promesse.
  2. Je mets en place les appels à resolve et à reject.
  3. Je fais un return de la promesse. Ca va me permettre d'attacher les callbacks à l'extérieur de la fonction.
  4. J'appelle la fonction sur une première uri asyncIO(uri_1). Je vais récupérer un numéro de client.
  5. J'appelle une deuxième fois la fonction sur une deuxième uri asyncIO(uri_2). Le return me permet de retourner une nouvelle promesse qui correspond à une deuxième requête et de chaîner un then. Je vais récupérer une liste de commande (une simple chaîne pour le test).
  6. Avec cette technique, les déclarations des callbacks sont placées les unes sous les autres, au fur et à mesure que les requêtes s'enchaînent. Le code est parfaitement lisible. Notre objectif est atteint.

Ci dessous, le code php qui est installé côté serveur. On traite deux "actions". Dans la première, on renvoie le numéro de client. Dans la deuxième, la liste de commande de ce client. La deuxième action ne peut se faire qu'après l'exécution de la première.

Tester le code
Résultat avec deux réussites
Réussite des deux opérations
Tester le code
Résultat avec un échec provoqué par une erreur volontaire dans l'uri_2
Echec d'une opération