samedi 15 décembre 2012

L'API Promise d'AngularJS

Les services standards d'AngularJS $timeout et surtout $http renvoient tous deux des promises, qui sont très pratiques pour gérer des opérations asynchrones. Cette notion de promise existe dans d'autres frameworks, comme jQuery, et AngularJS intègre une implémentation de cette API. Elle peut être utilisée par les développeurs dans l'écriture des leurs propres services pour simplifier la gestion des actions asynchrones. C'est très important de bien comprendre comment fonctionne cette API, qui est probablement la partie la plus ardue d'AngularJS, pour tirer profit de toute la puissance du service $http, et pour gérer facilement les enchaînements d'opérations asynchrones dans une application.

Décrire en français le fonctionnement de l'API de promises ne va pas être simple, car il est difficile de traduire de façon élégante les notions qu'elle recouvre sans s'éloigner des termes anglais utilisés comme noms de méthodes. Donc tant pis pour l'élégance, je vais m'en tenir aux termes anglais ou à des traductions mot-à-mot, pour coller au plus près aux noms des méthodes disponibles.

Qu'est-ce qu'une promise ? 

Une promise (une “promesse” en anglais) est un objet JavaScript correspondant au résultat différé d'une opération asynchrone.

Imaginons une fonction qui doit déclencher une opération prenant un certain temps, et qui pourra soit réussir et fournir un résultat, soit échouer et balancer une exception. Ça peut être une fonction synchrone, mais elle va devoir bloquer l'exécution jusqu'à ce que l'opération soit terminée, pour selon le cas renvoyer la valeur de retour ou balancer l'exception.

Dans un langage multi-thread on peut envisager éventuellement de bloquer ainsi l'exécution, pas en JavaScript. Du coup on va préférer un fonctionnement asynchrone : la fonction va juste démarre l'opération, et renvoyer immédiatement une promise, une promesse de résultat différé, qui sera résolue ultérieurement comme une valeur de retour ou comme une cause d'erreur, l'équivalent d'une exception.


Les promises ont une méthode then(), c'est même à ça qu'on les reconnaît. L'API spécifie, et c'est ce qui est fait dans AngularJS, qu'un objet est considéré comme une promise si et seulement si il a une méthode then(). Cette méthode then() prend deux fonctions comme paramètres, un successCallback et un errorCallback, et l'un ou l'autre sera appelé selon si la promise est résolue avec succès ou en erreur :

promise.then(function(greeting) {
    alert('Success: ' + greeting);
}, function(reason) {
    alert('Failed: ' + reason);
});

L'API des promises garantit que :

  1. la méthode then() d'une promise prend deux callbacks en paramètres, tous deux optionnels ; le premier sera appelé si la promise est résolue avec succès, le second en cas d'erreur.
  2. on peut enregistrer plusieurs paires de callbacks (succès et erreur), en appelant plusieurs fois la méthode then() d'une promise
  3. si plusieurs paires de callbacks ont été enregistrées sur une même promise, la résolution de cette promise déclenchera une seule fois le callback adéquat de chacune des paires, et forcément dans l'ordre où les paires de callbacks ont été enregistrées
  4. on peut appeler la méthode then() d'une promise déjà résolue, dans ce cas le callback adéquat sera appelé immédiatement - mais jamais avant la fin de la méthode then()
  5. une promise ne peut être résolue qu'une seule fois, donc tous les callbacks verront le même résultat, ou la même erreur
  6. la méthode then() renvoie une nouvelle promise, qui représente le résultat différé du callback appelé
  7. les promises peuvent être utilisées dans les vues AngularJS comme des données différées, qui seront automatiquement prises en compte dans les bindings lors de la résolution de chaque promise

Tous les points sont importants dans cette liste. Mais un premier aspect fondamental de l'API des promises est indiqué dans le point 4 : on peut enregistrer un callback n'importe quand, sans se préoccuper de savoir si la promise est déjà résolue, autrement dit si l'opération asynchrone s'est déjà terminée ou est encore en cours d'exécution. Peu importe, on enregistre une paire de callbacks sur la promise, et on est sûr que le callback approprié sera appelé dès que possible.

Les points 6 et 7 ont de grosses conséquences, et nous allons les détailler.

Enchaînement de promises

Commençons par le point 6 : c'est ce qui permet de chaîner des promises. La méthode then() renvoie une seconde promise, dont l'opération asynchrone est le callback de la première promise. Si ce callback renvoie une valeur quelconque, la promise retournée par then() sera résolue dès la fin du callback. Mais, et là accrochez-vous au pinceau j'enlève l'échelle, le callback peut lui-même renvoyer une autre promise. Nous voilà avec trois promises dans l'histoire, on va prendre un exemple avec des noms de variables pour que ce soit compréhensible :

var promise2 = promise1.then(function () {
    ...
    return callbackPromise;
});

Dans cette exemple où la fonction callback renvoie une promise callbackPromise, la promise promise2 retournée par then() sera chaînée à callbackPromise, et résolue lorsque callbackPromise sera résolue. En pratique dans le code d'AngularJS, la promise retournée par then() est toujours chaînée à la valeur retour du callback, qui si ce n'est pas une promise est encapsulée dans une promise résolue en succès avec cette valeur-là.

Donc si le callback déclenche une opération asynchrone, il faut absolument qu'il renvoie une promise qui ne sera résolue que lorsque l'opération asynchrone sera complètement terminée. Si le callback ne renvoie rien, ou une valeur qui n'est pas une promise, l'enchaînement se fera trop tôt, sans attendre la fin de l'opération asynchrone. Il vaut mieux gérer correctement le retour du callback même s'il n'y a rien à enchaîner derrière, comme ça le jour où l'on aura besoin d'ajouter un enchaînement ça marchera directement. On évite ainsi de créer une cause d'erreur potentielle, sur laquelle on butera certainement un jour.

Revenons-en à cette seconde promise retournée par then(). On peut évidemment lui enregistrer un nouveau callback, ou une paire de callbacks, en appelant then() qui renvoie alors une troisième promise. Et ainsi de suite. On peut ainsi enchaîner très facilement sous la forme d'un code linéaire des opérations asynchrones, dont chacune ne démarrera qu'à la fin de la précédente, et seulement s'il n'y a eu aucune erreur dans toutes les opérations précédentes :

promise.then(step1)
       .then(step2)
       .then(step3)
       .then(step4)
       .then(null, function (error) {
           // Handle any error from step1 through step4
       });

Les quatre premiers appels à then() ne passent que le premier paramètre, le callback à appeler en cas de succès. Les deux paramètres de then() sont optionnels. Pour une promise qui sera résolue en erreur, si l'on appelle sa méthode then() sans second paramètre, alors ça ne déclenchera pas le callback d'erreur puisqu'il est absent, mais la nouvelle promise qui a été renvoyée par then() sera elle aussi résolue en erreur, avec la même cause d'erreur. Il faut voir ça comme une exception qui remonte dans la pile d'appel des fonctions, et voir le callback d'erreur comme un catch. Dans un tel enchaînement de promises sans traitement d'erreur, si une erreur survient, toutes les promises suivante vont zapper le callback de succès. Ici s'il y a une erreur dans la fonction step2, alors les fonctions step3 et step4 ne seront pas appelées, et seul le callback d'erreur qui est dans le dernier then() sera exécuté. Bien sûr s'il y avait eu un callback d'erreur avec la fonction step3, c'est celui-là qui aurait été exécuté, mais il est souvent pertinent de ne faire la gestion des erreurs que de façon globale à la fin de l'enchaînement.

Sans promises, si l'on veut faire la même chose directement avec des callbacks, pour un tel exemple on se retrouve avec 4 niveaux de callbacks imbriqués, et la gestion des erreurs à faire dans les 4 niveaux. Et si le nombre d'étapes est variable, il faudrait faire appel à de la récursivité, alors qu'avec des promises une simple boucle suffit. L'utilisation des promises permet de mettre le code à plat, presque comme s'il était synchrone, avec en plus la propagation des erreurs. C'est une très grosse simplification.

Car ce besoin d'enchaîner des opérations asynchrones, c'est quelque chose de très courant dans une application cliente JavaScript. Typiquement, quand on accède à une API web de type REST, on va régulièrement devoir enchaîner des requêtes HTTP, parce qu'une requête aura besoin d'une valeur retournée par une ou plusieurs requêtes précédentes. Autant dire que si l'on veut utiliser des API de type REST avec AngularJS, mieux vaut maîtriser les promises, pour ne pas se noyer dans de multiples niveaux de callbacks imbriqués.

Je disais que le callback d'erreur est l'équivalent d'un catch. L'analogie va plus loin, car le callback d'erreur s'il est au milieu d'un enchaînement, peut soit arriver à corriger l'erreur et permettre l'exécution normale des étapes suivantes, soit ne pas arriver à la corriger et balancer une nouvelle erreur, comme un throw ou rethrow. Car la promise renvoyée par la méthode then() sera chaînée de la même façon à la promise retournée par le callback, que ce soit le callback de succès ou le callback d'erreur qui ait été appelé. Si le callback d'erreur renvoie une promise résolue en erreur, ça veut dire que l'erreur persiste et n'a pas pu être corrigée, mais s'il renvoie autre chose, une valeur qui n'est pas une promise, ou une promise qui sera résolue en succès, ou même s'il ne renvoie rien, alors il n'y a plus d'erreur et la promise suivante de l'enchaînement sera résolue en succès. Et s'il survient une exception dans le callback et qu'elle n'est pas interceptée, alors la méthode then() l'intercepte et l'encapsule dans une promise résolue en erreur pour la suite de l'enchaînement.

Le promise manager : $q.defer()

J'ai parlé jusque là des promises et de la façon de les enchaîner, sans expliquer comment on peut créer une promise correspondant à une opération asynchrone.

C'est là qu'intervient le promise manager. On crée un promise manager en appelant la méthode defer() du service $q d'AngularJS. Ce service tire son nom de la “Q Library” de Kris Kowal, dont est directement inspirée l'implémentation des promises dans AngularJS.

$q.defer() crée un promise manager, selon le terme employé dans la spécification de Kris Kowal. C'est un objet qui a une propriété promise contenant logiquement la promise associée, ainsi que deux méthodes :
  • resolve(value) qui sert à résoudre la promise associée, en lui fournissant la valeur de retour (le résultat de l'opération)
  • reject(reason) qui sert à résoudre en erreur la promise, en fournissant la raison de l'erreur
L'appel de la méthode reject() entraîne forcément un rejet de la promise, c'est-à-dire sa résolution en erreur.

Pour la méthode resolve() c'est plus compliqué. Si la valeur passée n'est pas une promise, alors la promise associée au promise manager est résolue en succès, avec cette valeur comme résultat. Mais si on passe une autre promise, appelons-là promise0, à la méthode resolve(), alors c'est seulement quand promise0 sera résolue que la promise associée au promise manager sera elle-aussi résolue de la même façon, en succès avec la même valeur si promise0 est résolue en succès, ou en erreur avec la même cause si promise0 est résolue en erreur. On retrouve ici le mécanisme qui sert à chaîner les promises.

En fait la méthode reject() n'est qu'un cas particulier de resolve() puisque ça revient à appeler resolve() en encapsulant la cause d'erreur dans une promise rejetée en erreur, c'est exactement ce qui est fait dans le code d'AngularJS (méthode reject() de l'objet renvoyé par $q.defer()) :

    reject: function(reason) {
        deferred.resolve(reject(reason));
    },

Une telle promise rejetée en erreur peut être créée directement grâce au service $q :

$q.reject(reason);

Prenons un exemple tout simple avec une opération différée via un setTimeout(). Il a l'avantage d'être simple, mais en pratique c'est quelque chose qu'on n'écrira jamais ainsi, car AngularJS fournit le service $timeout pour ça. Peu importe, c'est un exemple pour montrer le mécanisme, et pas un exemple de ce qu'il faut faire (le jsFiddle est ici) :

var AppCtrl = function($scope, $q) {
    var deferred = $q.defer();
    setTimeout(function() {
        $scope.$apply(function() {
            deferred.resolve("terminé !");
        });
    }, 3000);
    var promise = deferred.promise;

    promise.then(function(result) {
        $scope.result = result;
    });
}​

Le contenu de la fonction différée par setTimeout() est encapsulé dans un $scope.$apply(), pour que le code soit exécuté dans le contexte d'AngularJS. C'est justement pour ne pas avoir à faire ça qu'on utilisera en pratique plutôt le service $timeout. Donc dans cet exemple, on crée un objet deferred, un manager de promise, et on attache un callback à la promise grâce à sa méthode then(). Quand le timeout de 3 secondes s'achève, le manager résout la promise, en succès avec comme valeur la chaîne "terminé !". Ce qui déclenche le callback enregistré avec then(), la valeur est mise dans le scope, et s'affiche dans la vue.

Le service $q fournit deux autres méthodes intéressantes. $q.when(value) encapsule la valeur passée en paramètre dans une promise. Ca permet de créer une promise déjà résolue en succès avec une certaine valeur, par exemple pour servir de point de départ pour l'enchaînement de plusieurs étapes :

var startPromise = $q.when(value);
startPromise.then(step1)
            .then(step2)
            .then(step3)
            .then(step4)
            .then(null, function (error) {
                // Handle any error from step1 through step4
            });

Si on passe à la méthode $q.when() un objet qui est déjà une promise, elle renvoie non pas la promise telle quelle, mais une nouvelle promise équivalente car elle est chaînée à celle passée en paramètre, donc qui sera résolue en même temps et de la même façon.

La dernière méthode, $q.all([promise1, promise2, promise3]), sert à créer une promise qui sera résolue lorsque toutes les promises du tableau passé en paramètre seront elles-mêmes résolues. S'il y a dans le tableau des valeurs qui ne sont pas des promises, chacune est encapsulée dans une promise déjà résolue en succès avec la valeur en question, comme le ferait la méthode $q.when(). Si toutes les promises du tableau sont résolues en succès, alors la promise renvoyé par $q.all() est résolue en succès, avec comme valeur résultat un tableau contenant les résultats de chacune des promises, dans le même ordre bien sûr. Si une promise du tableau passé à $q.all() est rejetée en échec, alors la promise renvoyée est elle aussi rejetée en échec, avec la même cause.

A quoi ça sert ? Tout simplement à créer un point de synchronisation, dans un enchaînement qui n'est plus simplement linéaire. On peut avoir une opération qui dépend du résultat de plusieurs autres opérations, lesquelles peuvent être simultanées. Avec $q.all(), on crée une promise qui sera résolue quand toutes les opérations simultanées seront terminées, et on y attache l'opération suivante, qui sera alors exécutée dès que possible mais forcément après tous ses prérequis. Ça peut être typiquement une requête HTTP à un service web, qui a besoin d'informations fournies par plusieurs requêtes préalables.

Les promises du service $http

Une requête HTTP étant clairement une opération asynchrone, c'est donc en toute logique que le service $http d'AngularJS renvoie une promise. Qu'on utilise directement la fonction $http() en lui passant des paramètres, où l'une des méthodes simplifiées $http.get(), $http.post(), etc., toutes renvoient une promise.

Il s'agit d'une vraie promise, donc avec une méthode then(), mais qui dispose aussi de deux méthodes supplémentaires, success() et error() :

$http({method: 'GET', url: '/someUrl'}).
    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 status
        // code outside of the [200,300[ interval
    });

On pourrait utiliser la méthode then() pour enregistrer les callbacks traitant le retour de la requête HTTP, suivant si c'est un succès ou une erreur. Mais les méthodes success() et error() sont un peu plus pratiques, car le callback qu'on leur passe reçoit directement quatre paramètres qui décomposent le contenu de la réponse HTTP, parsé si c'était du JSON, le statut HTTP, les headers et la configuration.

Tandis que si on utilise la méthode then(), le callback appelé recevra en paramètre un objet avec quatre propriétés : data, status, headers et config. Donc ce n'est qu'un petit raccourci, qui évite d'aller chercher dans les propriétés d'un unique objet paramètre.

Par contre là où ça diffère davantage, c'est au niveau des valeurs renvoyées. Alors que la méthode then() crée et renvoie une nouvelle promise chaînée au retour du callback, les méthodes success() et error() ne créent pas de nouvelle promise, et renvoient l'objet this sur lequel elles sont appelées, c'est-à-dire la promise créée par $http() ou $http.get() ou la forme utilisée quelle qu'elle soit.

C'est pour ça qu'on peut faire suivre les appels à success() et error(), dans l'ordre qu'on veut d'ailleurs. Mais c'est aussi pour ça que les méthodes success() et error(), contrairement à la méthode then(), ne permettent pas d'enchaîner des requêtes HTTP sous la forme d'un chaînage de promises.

Rien n'empêche de combiner ces deux types de méthodes, d'utiliser success() et éventuellement error() pour traiter le résultat de la requête, et la méthode then() de la même promise pour l'enchaînement de la requête suivante. Voyons ça au travers d'un exemple, qui consiste à enchaîner deux requêtes à l'API de Twitter - l'ancienne API en réalité, qui ne demandait pas d'être authentifié, c'est plus simple et pour l'instant elle marche encore. La première requête va cherche les derniers tweets contenant la chaîne de caractères "AngularJS". On ne garde que le tweet le plus récent, c'est-à-dire l'élément 0 du tableau reçu car ils sont triés par date décroissante, pour trouver son auteur. Et on fait une seconde requête qui récupère les derniers tweets de cet auteur-là. L'intérêt n'est pas flagrant, mais c'est juste pour montrer un enchaînement de requêtes, la seconde utilisant des données récupérées par la première.

On peut faire la première requête, puis la seconde à l'intérieur du callbalk passé à la méthode success() de la première. C'est ce qui est fait dans ce premier jsFiddle :

var deferred = $q.defer();

var url = 'http://search.twitter.com/search.json'
        + '?q=angularjs&rpp=100&include_entities=true&result_type=mixed'
        + '&callback=JSON_CALLBACK';
$http.jsonp(url).
    success(function(data, status) {
        var lastTweet = data.results[0];
        var user = {
            screenName: lastTweet.from_user,
            name: lastTweet.from_user_name,
            image: lastTweet.profile_image_url
        };
        var url = 'https://api.twitter.com/1/statuses/user_timeline.json'
                + '?screen_name='
                + encodeURIComponent(lastTweet.from_user)
                + '&callback=JSON_CALLBACK';
        $http.jsonp(url).
            success(function(data, status) {
                deferred.resolve({user: user, lastTweets: data});
            }).error(function(data, status) {
                deferred.reject(data);
            });
    }).
    error(function(data, status) {
        deferred.reject(data);
    });

return deferred.promise;            

L'ensemble est dans un service qui renvoie une promise, créée en appelant $q.defer(). Ça marche, mais on voit bien qu'on a dans le code un niveau d'imbrication supplémentaire pour chaque nouvelle requête à enchaîner, et que la gestion des erreurs doit se faire à chaque requête. Si ce n'est pas critique ici parce qu'il y a seulement deux requêtes, il est difficilement envisageable d'écrire des enchaînements plus complexes de cette façon.

Mais si le code de ce premier exemple est imbriqué, c'est parce qu'on n'a pas utilisé les possibilités d'enchaînement des promises. L'objet renvoyé par $http.jsonp() est une promise, profitons-en.

Voici un second jsFiddle, où l'enchaînement se fait en appelant la méthode then() de la promise :

var url, user, lastTweets;
var promiseStart = $q.when('start');

var promise1 = promiseStart.then(function (value) {
    url = 'http://search.twitter.com/search.json'
        + '?q=angularjs&rpp=100&include_entities=true&result_type=mixed'
        + '&callback=JSON_CALLBACK';
    return $http.jsonp(url).
        success(function(data, status) {
            var lastTweet = data.results[0];
            user = {
                screenName: lastTweet.from_user,
                name: lastTweet.from_user_name,
                image: lastTweet.profile_image_url
            };
        });
});
    
var promise2 = promise1.then(function (value) {
    url = 'https://api.twitter.com/1/statuses/user_timeline.json'
        + '?screen_name='
        + encodeURIComponent(user.screenName)
        + '&callback=JSON_CALLBACK';
    return $http.jsonp(url).
        success(function(data, status) {
            lastTweets = data;
        });
});

var promiseEnd = promise2.then(function (value) {
    // Success of all the chained requests
    return {user: user, lastTweets: lastTweets};
}, function (reason) {
    // Error in any request
    return $q.reject(reason);
});

return promiseEnd;    

Cette fois on a un code à plat, on pourrait enchaîner ainsi autant de requêtes qu'on veut sans aucune imbrication. On n'a pas besoin non plus de créer de promise manager avec $q.defer(). Il y a une première promise déjà résolue, promiseStart, qui est créé en appelant $q.when(). Ça n'est pas indispensable, c'est juste pour des raisons de symétrie du code, pour que toutes les étapes aient la même structure que je préfère mettre aussi la première étape dans un then(). Et chaque callback passé à then(), qui contient le code d'une étape, avec donc une requête HTTP, renvoie directement la promise de la requête HTTP. Ce qui fait que then() construit une promise suivante chaînée à celle de la requête HTTP, et qui sera donc résolue quand la réponse HTTP aura été reçue. Mais elle ne sera résolue qu'après exécution du callback enregistré par la méthode success(), car l'API des promises garantit que les callbacks seront appelés dans leur ordre d'enregistrement. Ici on voit bien que l'ordre est important, si le callback du then() suivant était appelé avant celui de success(), ça ne pourrait pas fonctionner. J'ai mis des logs dans le jsFiddle, pour que vous puissiez voir dans la console l'enchaînement des appels.

Tout à la fin, on construit une promise finale, promiseEnd, en appelant encore then() sur la promise de la dernière étape, avec un callback de succès qui renvoie la valeur à passer à promiseEnd s'il n'y a pas eu d'erreur, et un callback d'erreur qui peut traiter une erreur survenue sur n'importe laquelle des requêtes HTTP successives. Et c'est cette promiseEnd qui est renvoyée par le service, immédiatement et bien sûr non résolue, la résolution de ces différentes promises chaînées se fera en cascade à mesure de l'arrivée des réponses aux requêtes HTTP.

Utilisation des promises dans les vues AngularJS

En regardant ces deux exemples, vous avez peut-être remarqué que ce que le contrôleur met dans le scope de la vue, c'est directement la promise renvoyée par le service. Mais qu'elle est utilisée dans la vue comme s'il s'agissait des vraies données. Nous voilà à ce que j'ai indiqué dans la liste tout au début, en point 7 : on peut utiliser les promises comme des données différées dans les vues d'AngularJS.

Le moteur de template d'AngularJS reconnaît les promises, au travers du code suivant du framework :

if (v && v.then) {
    p = v;
    if (!('$$v' in v)) {
        p.$$v = undefined;
        p.then(function(val) { p.$$v = val; });
    }
    v = v.$$v;
}

Si l'objet concerné par un binding a une méthode then(), ce qui veut dire que c'est une promise, AngularJS appelle sa méthode then() pour lui attacher un callback qui, lorsque la promise sera résolue en succès, va lui attacher le résultat dans une propriété $$v. Et le binding se fait en réalité sur cette propriété $$v. C'est ce mécanisme tout simple qui fait qu'on peut utiliser une promise dans la vue pour n'importe quel binding, pour faire une répétition comme sur l'exemple précédent des tweets, avec des bindings eux-mêmes sur les éléments répétés. Ainsi on ne voit rien dans la vue tant que la promise n'est pas résolue, puisque la propriété $$v n'existe pas, et tout apparaît comme par magie lors de la résolution de la promise, puisque d'un coup $$v existe, avec toutes les données à afficher.

Ça marche très bien, et il n'y a même pas besoin de connaître le fonctionnement interne, tant qu'il s'agit de données à afficher dans une vue. Pour faire des modifications, ça marche beaucoup moins bien, la magie a ses limites. Si on met des champs de formulaire avec des directives ng-model pointant sur les données différées d'une promise, les valeurs saisies par l'utilisateur vont être enregistrées dans l'objet promise lui-même, et non dans le résultat $$v attaché par AngularJS. A la soumission du formulaire, on aura les valeurs initiales reçues par la promise dans $$v, et seulement les propriétés modifiées qui auront été recopiées dans la promise. Difficile de faire une mise à jour vers le serveur à partir ce ça. Du coup pour alimenter un formulaire, je déconseille de mettre directement la promise dans le scope. Mais pour de l'affichage ça ne pose aucun problème.

Voilà pour ce tour d'horizon assez complet des possibilités offertes par l'API de promises d'AngularJS, qu'il vaut mieux avoir bien comprises, en particulier si l'on veut écrire un client faisant de nombreux accès à des services HTTP. Ça demande un peu d'habitude pour penser en promises plutôt qu'en imbrications de callbacks, mais à l'usage ça permet de résoudre assez facilement des enchaînements complexes d'opérations asynchrones.

12 commentaires:

  1. Très bien écrit et très intéressant !!

    merci

    RépondreSupprimer
  2. Merci pour cet article, et pour éclairer le problème de l'affichage de données qui peuvent aussi bien provenir d'un serveur (donc des promises) que de l'utilisateur.

    RépondreSupprimer
  3. Bel article qui explique très bien le fonctionnement des promises. Pour info, les jsfiddle ne marchent pas l'API de twitter ne supportant plus leur v1.

    RépondreSupprimer
  4. Pour info je crois que les versions récentes d'ANgular ne peuvent plus mettre des promises directement dans le scope pour la vue

    RépondreSupprimer
    Réponses
    1. En effet c'est une fonctionnalité désormais dépréciée. Voir https://github.com/angular/angular.js/commit/5dc35b527b3c99f6544b8cb52e93c6510d3ac577

      Supprimer
  5. Ce commentaire a été supprimé par l'auteur.

    RépondreSupprimer
  6. Bonjour, merci pour cet article !
    J'ai voulu tester le point 4 indiquant qu'on peut appeler la méthode then() d'une promise déjà résolue avec le code suivant :

    var AppCtrl = function($scope, $q) {

    var deferred = $q.defer();
    setTimeout(function() {
    $scope.$apply(function() {
    deferred.resolve("terminé !");
    });
    }, 3000);
    var promise = deferred.promise;

    setTimeout(function() {
    promise.then(function(result) {
    alert('result');
    $scope.result = result;
    });
    }, 5000);
    }

    Hors le callback de success n'est jamais appelé :(.
    Sauriez-vous me dire pouquoi ?

    Merci d'avance :)

    RépondreSupprimer
  7. Article, même ancien, très intéressant ! Merci beaucoup pour les éclaircissements.

    RépondreSupprimer