web-dev-qa-db-fra.com

AngularJS: Où utiliser les promesses?

J'ai vu des exemples de services de connexion à Facebook qui utilisaient des promesses pour accéder à 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 utilisaient "$scope.$digest() // Manual scope evaluation" quand la réponse leur était fournie

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?
  • Quels sont les raisons et les cas à utiliser $ q service?
  • Et comment ça marche ?
141
Maksym

Ce ne sera pas 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 un certain temps pour le comprendre.

Laissons 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 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
  ...

Il s'agit d'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.');
      }
  });

Il essaie d’abord de se connecter, puis ne demande à l’API Graph qu’après s’être assuré que l’ouverture de session a abouti.

Même dans ce cas, qui ne fait que chaîner deux opérations, les choses commencent à devenir compliquées. La méthode askFacebookForAuthentication accepte un rappel en cas d'échec et de succès, mais que se passe-t-il lorsque FB.login réussit mais que FB.api échoue? Cette méthode appelle toujours le rappel success quel que soit le résultat de la méthode FB.api.

Imaginez maintenant que vous essayez de coder une séquence solide de trois opérations asynchrones ou plus, de manière à gérer correctement les erreurs à chaque étape et à être lisible par quiconque ou même par vous au bout de quelques semaines. C'est possible, mais il est très facile de simplement imbriquer ces rappels et de perdre la trace des erreurs en cours de route.

Laissons maintenant de côté l'API Facebook et considérons l'API Angular Promises, telle que mise en œuvre par le service $q. Le modèle mis en œuvre 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 "jeter" une erreur à n'importe quelle étape de la voie et de la gérer à la fin, d'une manière sémantiquement similaire à la suivante. bloc familier try/catch.

Considérez cet exemple artificiel. Disons que nous avons deux fonctions, où la deuxième fonction consomme le résultat de la première:

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

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

 secondFn(firstFn()); 

Imaginons maintenant que firstFn et secondFn prennent tous deux beaucoup de temps, nous voulons donc traiter cette séquence de manière asynchrone. Tout d'abord, nous créons un nouvel objet deferred, qui représente une chaîne d'opérations:

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

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

Jusqu'à présent, notre promesse ne représente que le point de 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 ajouter 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é. Pour commencer, vous devez appeler deferred.resolve, en spécifiant la valeur initiale que vous souhaitez 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 de modèle sont correctement observées, Angular n'appelle pas la première étape de la chaîne avant la prochaine fois que $apply est appelé:

 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'ici, nous n'avons spécifié qu'un gestionnaire de réussite à chaque étape de la chaîne. then accepte également un gestionnaire d'erreurs comme second argument optionnel. Voici un autre exemple plus long de chaîne de promesse, cette fois avec 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 constater dans cet exemple, chaque gestionnaire de la chaîne a la possibilité de dévier le trafic vers le prochain gestionnaire error au lieu du gestionnaire suivant success. Dans la plupart des cas, vous pouvez avoir un seul gestionnaire d'erreurs à la fin de la chaîne, mais vous pouvez également avoir des gestionnaires d'erreurs 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 callback de Facebook à la manière dont Angular observe les modifications de modèle. Le premier exemple englobe l'appel d'API dans une promesse, qui peut être ajoutée à une portée et est comprise par le système de templates d'Angular. La seconde adopte une approche plus brutale consistant à définir le résultat du rappel directement sur l'étendue, puis à appeler $scope.$digest() pour informer Angular du changement provenant d'une source externe.

Les deux exemples ne sont pas directement comparables, car l'étape de connexion manque dans le premier. Cependant, il est généralement souhaitable d'encapsuler les interactions avec des API externes telles que celle-ci dans des services distincts et de fournir les résultats aux contrôleurs à la hauteur 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 factices.

401
karlgold

Je m'attendais à une réponse complexe qui couvrirait à la fois: pourquoi ils sont utilisés en général et comment les utiliser dans Angular

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

La source:

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

index.html

  <head>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.1.5/angular.js"></script>
    <script src="app.js"></script>
  </head>

  <body ng-app="myModule" ng-controller="HelloCtrl">
    <h1>Messages</h1>
    <ul>
      <li ng-repeat="message in messages">{{ message }}</li>
    </ul>
  </body>

</html>

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 que les extraits suivants sont 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 des modèles. - http://www.benlesh.com/2013/02/angularjs-creating-service-with-http.html

(l'exemple de plunker utilise 1.1.5.)

9
Mars Robertson

Un différé représente le résultat d'une opération asynchrone. Il expose une interface qui peut ê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 permettant d’interagir avec le différé qui lui est lié et permet ainsi aux parties intéressées d’avoir accès à l’état et au résultat de l’opération différée.

Lors de la création d’un compte différé, son état est en attente et il n’ya aucun résultat. Lorsque nous résolvons () ou rejetons () le différé, il change son état en résolu ou rejeté. Néanmoins, nous pouvons obtenir la promesse associée immédiatement après la création d’un compte différé et même affecter des interactions avec son résultat futur. Ces interactions ne se produiront qu'après le rejet ou la résolution du différé.

1
Ram G

utiliser la promesse au sein d'un contrôleur et s'assurer 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"]);
<!DOCTYPE html>
<html>

  <head>
    <script data-require="angular.js@*" data-semver="1.3.0-beta.5" src="https://code.angularjs.org/1.3.0-beta.5/angular.js"></script>
  </head>

  <body>
    <h1>Hello Angular</h1>
    <div ng-controller="test">
    </div>
  </body>

</html>
1
Manivannan A