Les promesses en JavaScript.
#1
Pré-requis
Il faut avoir compris la différence entre une opération synchrone et une opération asynchrone.
#2
Avantage d'une promesse
- 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 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. Si il y avait plus de code dans le corps des fonctions callback on aurait encore moins de lisibilité.
- 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.
#3
Objectif du tuto
Lorsque vous avez besoin de faire des opérations asynchrones vous pouvez vous retrouvez devant deux situations différentes :
- 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.
- Vous travaillez avec une Api qui n'utilise pas les promesses. C'est le cas des requêtes Ajax qui sont faite avec l'interface
XMLHttpRequest
. Dans ce cas vous pouvez placer un objetPromise
devant l'objetXMLHttpRequest
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.

#4
Qu'est ce qu'une promesse
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, et 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.

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.

#5
Le constructeur Promise
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
- 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. - 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é. - 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 duthen
la déclaration d'un callback qui dira ce qui faut faire dans cette situation de réussite. - La méthode
catch
qui sera appelée par la promesse lorsque l'opération échouera. On passera en argument ducatch
la déclaration d'un callback qui dira ce qu'il faut faire dans cette situation d'échec. - 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.

#6
Les différentes valeurs de l'état d'une promesse
La valeur de [[PromiseState]]
correspond à l'état de l'opération asynchrone. Les différentes valeurs sont les suivantes :
pending
pour une opération qui a été initiée. On dit que la promesse est en attente.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.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.settled
qui veut dire soitfulfilled
soitrejected
. 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
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 depending
àfulfilled
. Il faudra passer le résultat de l'opération en argument deresolve
. - Lorsque l'opération échouera il faudra appeler le callback
reject
. L'état de la promesse passera depending
àrejected
. Il faudra passer la raison de l'échec en argument dereject
.
Ces arguments sont stockés dans la variable interne [[PromiseResult]]
de l'objet Promise
.

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
.
resolve
avec la chaîne "Résultat de l'opération"
en argumentLa promesse passe de l'état
pending
à l'état fullfilled

reject
avec la chaîne "Raison de l'échec"
en argumentLa promesse passe de l'état
pending
à l'état rejected

#8
Déclarer les callbacks et les attacher
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.


#9
Promessifier une requête Ajax
Je vais écrire une nouvelle fonction pour promessifier une requête Ajax asynchrone.
- 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.
- Je mets en place les appels à
resolve
et àreject
. - Je fais un
return
de la promesse. Ca va me permettre d'attacher les callbacks à l'extérieur de la fonction. - J'appelle la fonction sur une première uri
asyncIO(uri_1)
. Je vais récupérer un numéro de client. - J'appelle une deuxième fois la fonction sur une deuxième uri
asyncIO(uri_2)
. Lereturn
me permet de retourner une nouvelle promesse qui correspond à une deuxième requête et de chaîner unthen
. Je vais récupérer une liste de commande (une simple chaîne pour le test). - 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
