Les promesses en AngularJS (2/3)

Nous avons vu dans l’article précédent en quoi consistait une promesse et comment fonctionnent les callbacks. Nous allons à présent voir les deux principaux utilisateurs de promesses dans AngularJS à savoir les services $http et $resource et quelles sont leurs spécificités.

Les services $http et $resource

Le service $http

Nous avons vu que pour greffer des callbacks à une promesse il fallait forcément utiliser then(). Et bien, ce n’est pas totalement vrai, du moins pas ici. Si c’est effectivement la bonne approche pour les promesses faites maison, celles renvoyées par les services $http et $resource en revanche nous exposent des fonctions supplémentaires plus pratiques pour traiter les résultats renvoyés. Je ne voulais juste pas commencer cette présentation avec des exceptions dès les premières lignes de code. Voici les fonctions spécifiques à $http que vous utiliserez pour récupérer le résultat de vos promesses :

success(function(data, status, headers, config)
error(function(data, status, headers, config)

Et voici un cas d’utilisation tiré directement de la documentation de $http :

$http.get('/users/1').success(function(data, status, headers, config) {
    // this callback will be called asynchronously when the response is available

    }).error(function(data, status, headers, config) {
    // called asynchronously if an error occurs or server returns response with an error status

});

Cela revient exactement à faire un then(successCallback, errorCallback) à la différence qu’ici le résultat de la promesse est découpé en 4 arguments distincts et ce sont ces derniers que l’on reçoit en arguments aux callbacks. Il est donc plus intéressant d’utiliser success() et error() qui sont plus explicites quant aux résultats envoyés par $http.

Vous trouverez ici un petit exemple d’appel à $http.get() utilisant then(), success(), error(), catch() ou encore finally().

Sachez également qu’il est possible avec $http de configurer des « interceptors » qui vous permettront d’intercepter toutes les requêtes ou les réponses qui passent par $http, donc respectivement entre la requête de votre application et le serveur et entre la réponse du serveur et votre application. Ces interceptors sont en fait eux-mêmes des promesses qui peuvent être résolues ou rejetées indépendamment de la requête initiale. On pourrait donc imaginer une gestion d’erreur centralisée où toutes les erreurs de type 404 NOT FOUND sur des images seraient interceptées afin de renvoyer une image par défaut à la place. Le code appelant recevrait donc une promesse résolue avec une image par défaut au lieu d’une promesse rejetée comme cela se serait produit sans l’interceptor.

Le service $resource

Ce service a été spécialement conçu pour interagir avec des API RESTful. Il s’agit en réalité d’une couche au dessus de $http qui facilite l’écriture de vos requêtes ainsi que la manipulation des résultats en sortie. La syntaxe est pour le coup très différente de $http :

var user = $resource('/users/:userId').get({userId: 1});

Ici beaucoup de choses se passent : dans un premier temps, on voit que l’on fournit une URL à un objet de classe $resource comme on l’aurait fait avec $http, à la différence qu’ici on peut y mettre des paramètres optionnels. On appelle ensuite la méthode get() sur cet objet et à qui on fournit justement la valeur du paramètre optionnel. Jusque là, la différence avec $http semble minime, néanmoins vous aurez peut-être remarqué deux choses :

  1. On a directement affecté le retour de la méthode get() à une variable user
  2. Il n’y a pas de then() ou de callback à la suite de get()

La raison est très simple : ce n’est pas une promesse que la méthode get() renvoie, mais un objet d’instance$resource qui représente la future donnée renvoyée. Si cet objet ne représente pas véritablement la promesse, il est tout de même possible de la récupérer par le biais de l’attribut $promise sur l’instance $resource :

$resource('/users/:userId').get({userId: 1}).$promise.then(function(value) {
    // successCallback

});

Néanmoins cela n’est pertinent que si vous avez explicitement besoin de la promesse. Si votre besoin se résume simplement à récupérer un résultat asynchrone, alors vous pouvez simplement utiliser la variable « user » à qui vous avez affecté le retour du get() et l’utiliser directement dans votre template HTML. En effet, cette dernière sera automatiquement remplacée par les données réelles renvoyées par l’instance $resource. En clair, la variable « user » ici représente les données futures de votre requête sans avoir à passer par un callback de succès comme vous auriez dû le faire avec une promesse. C’est d’ailleurs là toute la force de $resource car il nous évite de mettre à jour explicitement la valeur comme avec $http :

var user = {};

$http.get('/users/1').then(function(value) {
    user = value;
});

Oui mais si on veut quand même faire du traitement métier à la fin de la requête ? Faut-il forcément travailler avec la promesse originelle via $promise et appeler then() ?

On peut effectivement faire comme ça, mais le plus pratique est de passer par les callbacks de succès et d’erreur que l’on peut envoyer aux actions de l’objet d’instance$resource. Car oui, dans l’exemple précédent on s’est contenté de faire get({userId: 1}) sur la classe $resource mais en réalité cette « action » peut avoir trois arguments. Et ces deux arguments supplémentaires sont justement les callbacks de succès et d’erreur auxquels nous sommes à présent habitués. Voici la signature que présente la documentation officielle pour toutes les actions de type GET en HTTP :

Resource.action([parameters], [success], [error])

Petite parenthèse : La raison pour laquelle la documentation utilise le terme « action » ici et non de fonctions est très simple : il est possible de définir ses propres actions à la construction de la $resource et de définir leur signification HTTP ainsi que divers paramètres. Je pourrais donc si je le voulais créer une action recupere() qui en fait ferait la même chose que get().

La signature présentée précédemment ne concernent que les actions de type GET en HTTP. Il y a encore deux autres signatures pour deux situations supplémentaires :

  1. Les actions de type non GET sur la classe Resource :
    Resource.action([parameters], postData, [success], [error])
  2. Les actions de type non GET sur un objet d’instance Resource :
    instance.$action([parameters], [success], [error])

Notez la présence du symbole dollar « $ » pour les instances de $resource qui du coup va utiliser les données de l’instance dans le request body, tandis que la méthode de la classe $resource nécessite qu’on lui fournisse les données à envoyer. Je ne vais pas m’étendre dessus mais je vous invite fortement à voir la documentation pour bien assimiler la nuance entre la « classe » $resource et les « instances » de $resource.

Au final voici une utilisation de $resource classique dans laquelle on a également besoin d’effectuer du métier dans des callbacks de succès ou d’erreur :

var user = $resource('/users/:userId').get({userId: 1}, function(data) {
    // successCallback

    }, function(error) {
    // errorcallback

});

Ce sera tout pour le service $resource. Comme pour $http, vous trouverez ici un exemple d’appel à $resource().get() dans lequel les différents types d’appels possibles sont couverts.

Ce deuxième article sur la présentation des promesses dans AngularJS est à présent terminé. Nous avons vu ensemble les bases des promesses ainsi que les différents services d’AngularJS qui les utilisent, mais nous n’avons pas encore vu comment ces dernières fonctionnaient véritablement en interne ni comment créer ses propres promesses. Tout cela sera couvert dans le troisième et dernier article de cette présentation.