AngularJS: Où utiliser les promesses?

J’ai vu quelques exemples de services de connexion Facebook qui utilisaient les promesses d’access à l’API FB Graph.

Exemple # 1 :

this.api = function(item) { var deferred = $q.defer(); if (item) { facebook.FB.api('/' + item, function (result) { $rootScope.$apply(function () { if (angular.isUndefined(result.error)) { deferred.resolve(result); } else { deferred.reject(result.error); } }); }); } return deferred.promise; } 

Et les services qui ont utilisé "$scope.$digest() // Manual scope evaluation" lorsque la réponse a été reçue

Exemple # 2 :

 angular.module('HomePageModule', []).factory('facebookConnect', function() { return new function() { this.askFacebookForAuthentication = function(fail, success) { FB.login(function(response) { if (response.authResponse) { FB.api('/me', success); } else { fail('User cancelled login or did not fully authorize.'); } }); } } }); function ConnectCtrl(facebookConnect, $scope, $resource) { $scope.user = {} $scope.error = null; $scope.registerWithFacebook = function() { facebookConnect.askFacebookForAuthentication( function(reason) { // fail $scope.error = reason; }, function(user) { // success $scope.user = user $scope.$digest() // Manual scope evaluation }); } } 

JSFiddle

Les questions sont:

  • Quelle est la différence dans les exemples ci-dessus?
  • Quelles sont les raisons et les cas à utiliser le service $ q ?
  • Et comment ça marche ?

Cela ne va pas être une réponse complète à votre question, mais j’espère que cela vous aidera, vous et les autres, lorsque vous essayez de lire la documentation sur le service $q . Il m’a fallu du temps pour le comprendre.

Mettons de côté AngularJS pour un moment et considérons simplement les appels de l’API Facebook. Les deux appels d’API utilisent un mécanisme de rappel pour informer l’appelant lorsque la réponse de Facebook est disponible:

  facebook.FB.api('/' + item, function (result) { if (result.error) { // handle error } else { // handle success } }); // program continues while request is pending ... 

Ceci est un modèle standard pour la gestion des opérations asynchrones en JavaScript et dans d’autres langages.

Un gros problème avec ce modèle survient lorsque vous devez exécuter une séquence d’opérations asynchrones, où chaque opération successive dépend du résultat de l’opération précédente. C’est ce que fait ce code:

  FB.login(function(response) { if (response.authResponse) { FB.api('/me', success); } else { fail('User cancelled login or did not fully authorize.'); } }); 

Tout d’abord, il tente de se connecter, et seulement après avoir vérifié que la connexion a réussi, il envoie la requête à l’API Graph.

Même dans ce cas, qui ne fait qu’enchaîner deux opérations, les choses commencent à se gâter. La méthode askFacebookForAuthentication accepte un rappel pour échec et réussite, mais que se passe-t-il lorsque FB.login réussit mais que FB.api échoue? Cette méthode appelle toujours le rappel de success quel que soit le résultat de la méthode FB.api .

Imaginez maintenant que vous essayez de coder une séquence robuste de trois opérations asynchrones ou plus, de manière à gérer correctement les erreurs à chaque étape et à être lisibles pour tout le monde ou même après quelques semaines. Possible, mais il est très facile de continuer à imbriquer ces rappels et de perdre la trace des erreurs en cours de route.

Maintenant, mettons de côté l’API Facebook pour un moment et considérons simplement l’API Angular Promises, telle qu’elle est implémentée par le service $q . Le modèle implémenté par ce service est une tentative pour transformer la programmation asynchrone en quelque chose qui ressemble à une série linéaire d’instructions simples, avec la possibilité de “lancer” une erreur à n’importe quelle étape et de la gérer de manière sémantique similaire à bloc try/catch familier.

Considérez cet exemple artificiel. Disons que nous avons deux fonctions, où la deuxième fonction consum le résultat du premier:

  var firstFn = function(param) { // do something with param return 'firstResult'; }; var secondFn = function(param) { // do something with param return 'secondResult'; }; secondFn(firstFn()); 

Maintenant, imaginez que firstFn et secondFn prennent tous les deux beaucoup de temps, nous voulons donc traiter cette séquence de manière asynchrone. Nous créons d’abord un nouvel object deferred , qui représente une chaîne d’opérations:

  var deferred = $q.defer(); var promise = deferred.promise; 

La propriété de la promise représente le résultat final de la chaîne. Si vous vous connectez une promesse immédiatement après l’avoir créée, vous verrez qu’il ne s’agit que d’un object vide ( {} ). Rien à voir pour le moment, avancez bien.

Jusqu’à présent, notre promesse ne représente que le sharepoint départ de la chaîne. Ajoutons maintenant nos deux opérations:

  promise = promise.then(firstFn).then(secondFn); 

La méthode then ajoute une étape à la chaîne, puis renvoie une nouvelle promesse représentant le résultat final de la chaîne étendue. Vous pouvez append autant d’étapes que vous le souhaitez.

Jusqu’à présent, nous avons mis en place notre chaîne de fonctions, mais rien ne s’est réellement passé. Vous avez commencé les choses en appelant deferred.resolve , en spécifiant la valeur initiale à transmettre à la première étape de la chaîne:

  deferred.resolve('initial value'); 

Et puis … toujours rien ne se passe. Pour s’assurer que les modifications du modèle sont correctement observées, Angular n’appelle pas la première étape de la chaîne avant que la prochaine $apply soit appelée:

  deferred.resolve('initial value'); $rootScope.$apply(); // or $rootScope.$apply(function() { deferred.resolve('initial value'); }); 

Alors qu’en est-il de la gestion des erreurs? Jusqu’à présent, nous n’avons spécifié qu’un gestionnaire de réussite à chaque étape de la chaîne. then accepte également un gestionnaire d’erreur en tant que second argument facultatif. Voici un autre exemple plus long d’une chaîne de promesses, cette fois avec la gestion des erreurs:

  var firstFn = function(param) { // do something with param if (param == 'bad value') { return $q.reject('invalid value'); } else { return 'firstResult'; } }; var secondFn = function(param) { // do something with param if (param == 'bad value') { return $q.reject('invalid value'); } else { return 'secondResult'; } }; var thirdFn = function(param) { // do something with param return 'thirdResult'; }; var errorFn = function(message) { // handle error }; var deferred = $q.defer(); var promise = deferred.promise.then(firstFn).then(secondFn).then(thirdFn, errorFn); 

Comme vous pouvez le voir dans cet exemple, chaque gestionnaire de la chaîne peut redirect le trafic vers le gestionnaire d’ erreurs suivant au lieu du gestionnaire de succès suivant. Dans la plupart des cas, vous pouvez avoir un seul gestionnaire d’erreur à la fin de la chaîne, mais vous pouvez également avoir des gestionnaires d’erreur intermédiaires qui tentent une récupération.

Pour revenir rapidement à vos exemples (et à vos questions), je dirai simplement qu’ils représentent deux manières différentes d’adapter l’API orientée sur le rappel de Facebook à la façon dont Angular observe les modifications du modèle. Le premier exemple englobe l’appel d’API dans une promesse, qui peut être ajoutée à une scope et comprise par le système de modélisation d’Angular. Le second prend l’approche plus brutale consistant à définir le résultat du rappel directement sur la scope, puis à appeler $scope.$digest() pour que Angular soit informé du changement depuis une source externe.

Les deux exemples ne sont pas directement comparables, car le premier manque l’étape de connexion. Cependant, il est généralement souhaitable d’encapsuler les interactions avec des API externes telles que celles-ci dans des services séparés et de fournir les résultats aux contrôleurs comme des promesses. De cette façon, vous pouvez séparer vos contrôleurs des problèmes externes et les tester plus facilement avec des services simulés.

Je m’attendais à une réponse complexe qui couvrira les deux: pourquoi ils sont utilisés en général et comment l’utiliser dans Angular

C’est le plunk pour les promesses angulars MVP (promesse viable minimale) : http://plnkr.co/edit/QBAB0usWXc96TnxqKhuA?p=preview

La source:

(pour ceux qui sont trop paresseux pour cliquer sur les liens)

index.html

       

Messages

  • {{ message }}

app.js

 angular.module('myModule', []) .factory('HelloWorld', function($q, $timeout) { var getMessages = function() { var deferred = $q.defer(); $timeout(function() { deferred.resolve(['Hello', 'world']); }, 2000); return deferred.promise; }; return { getMessages: getMessages }; }) .controller('HelloCtrl', function($scope, HelloWorld) { $scope.messages = HelloWorld.getMessages(); }); 

(Je sais que cela ne résout pas votre exemple spécifique sur Facebook, mais je trouve les extraits suivants utiles)

Via: http://markdalgleish.com/2013/06/using-promises-in-angularjs-views/


Mise à jour du 28 février 2014: à partir de la version 1.2.0, les promesses ne sont plus résolues par les modèles. http://www.benlesh.com/2013/02/angularjs-creating-service-with-http.html

(exemple plunker utilise 1.1.5.)

Un différé représente le résultat d’une opération asynchrone. Il expose une interface pouvant être utilisée pour signaler l’état et le résultat de l’opération qu’il représente. Il fournit également un moyen d’obtenir l’instance de promesse associée.

Une promesse fournit une interface pour interagir avec son différé, et permet ainsi aux parties intéressées d’avoir access à l’état et au résultat de l’opération différée.

Lors de la création d’un différé, son état est en attente et n’a aucun résultat. Lorsque nous résolvons () ou rejetons () le différé, il change son état à résolu ou rejeté. Cependant, nous pouvons obtenir la promesse associée immédiatement après avoir créé un différé et même atsortingbuer des interactions avec son résultat futur. Ces interactions se produiront seulement après le différé rejeté ou résolu.

utiliser promis dans un contrôleur et assurez-vous que les données sont disponibles ou non

  var app = angular.module("app",[]); app.controller("test",function($scope,$q){ var deferred = $q.defer(); deferred.resolve("Hi"); deferred.promise.then(function(data){ console.log(data); }) }); angular.bootstrap(document,["app"]); 
       

Hello Angular