Promise
Dernière mise à jour
Dernière mise à jour
Le concept de Promise
date des années 70. Les Promise
s sont parfois appelés Future
ou Deferred
.
En 2010, le développeur Kris Kowal inspire la communauté JavaScript en implémentant ce concept pour NodeJS via la librairie Q https://github.com/kriskowal/q. Les implémentations se sont ensuite démultipliées jusqu'à ce que les Promise
s deviennent un standard avec ES6.
Promise
sLe fonctionnement d'une Promise
est généralement le suivant :
1. Un appelant fait appel à une fonction qui procède à un traitement asynchrone mais retourne de façon synchrone un objet container (la Promise
) à l'appelant.
2. A ce stade, la Promise
est le plus souvent dans un état "pending".
3. L'appelant inscrit sur la Promise
une "callback" de succès pour être informé quand le résultat est disponible (e.g. : promise.then(data => console.log(data)
) et une callback d'erreur pour être informé de l'échec (e.g. : promise.catch(error => console.error(error)
).
4. Quand la fonction appelée obtient le résultat (ou une erreur), elle notifie la Promise
qui passe alors à un état "resolved" (ou "rejected").
5. La Promise
déclenche alors toutes les fonctions de "callback" de succès (ou d'erreur) qui ont pu lui être transmises (via les méthodes .then
et .catch
).
Promise
A titre d'exemple, nous allons utiliser la fonction fetch
désormais standard qui vient déloger la poussiéreuse XMLHttpRequest
.
Cette fonction a la particularité de retourner une Promise
.
Pour accéder au "body" de la response, il faut utiliser la méthode Response.json
qui retourne une Promise
également.
Malheureusement, pour le moment, les problèmes associés au "callbacks waterfall" persistent.
Promise
sPour éviter les "callbacks waterfall", il est possible de chainer les Promise
. En effet, les méthodes Promise.then
et Promise.catch
retournent des Promise
s.
La ligne 1 retourne une Promise
contenant la Response
(elle est donc de type Promise<Response>
).
La ligne 2 crée à son tour une nouvelle Promise
déduite de la première et contenant le "status" (elle est donc de type Promise<number>
).
La ligne 3 consomme donc le résultat de la Promise
précédente.
Une autre propriété intéressante des Promise
est que si la valeur retournée à l'une des étapes est une Promise
, alors l'étape suivante ne sera appelée que quand la Promise
sera "resolved" et elle recevra en paramètre le résultat de résolution.
Si une erreur se produit à n'importe quelle étape soit car :
la Promise
initiale est "rejected",
l'une des étapes lève une exception,
ou l'une des étapes retourne une Promise
"rejected,
alors toutes les étapes suivantes sont ignorées et la "callback" associée au premier "catch" de la chaîne (à partir de l'erreur) est appelé.
Nous obtenons alors l'erreur response.JSON is not a function
à la ligne 4 (car la méthode se nomme json
et non JSON
).
Promise
sPour créer une Promise
, il faut instancier la classe Promise
tel qu'indiqué ci-dessous et appeler la fonction "resolve" avec la donnée de résolution en cas de succès ou la méthode "reject" avec l'objet d'erreur en cas d'échec.
Une Promise
ne peut être "resolved" ou "rejected" qu'une seule fois.
C'est le premier appel qui gagne et qui définit donc l'état final de la Promise
, les appels suivants sont simplement ignorés.
Vivement le jour où les appels superflus de "resolve" et "reject" déclencheront des erreurs.
Parmi d'autres limitations que nous aborderons plus tard, dans le dernier exemple, on peut remarquer l'indisponibilité de la variable city
lors du console.log
de l'étape finale (ligne 23).
Il existe bien sûr des solutions de contournement mais peu séduisantes. Il est préférable d'adopter directement l'approche Async / Await pour éviter ces problèmes.