AngularJS: Wo kann man Versprechen verwenden?


141

Ich habe einige Beispiele für Facebook-Anmeldedienste gesehen, die Versprechen für den Zugriff auf die FB Graph-API verwendeten.

Beispiel 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;
}

Und Dienste, die verwendet wurden, "$scope.$digest() // Manual scope evaluation"als die Antwort erhalten wurde

Beispiel 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

Die Fragen sind:

  • Was ist der Unterschied in den obigen Beispielen?
  • Was sind die Gründe und Fälle für die Nutzung des $ q- Dienstes?
  • Und wie funktioniert es funktioniert ?

9
Klingt so, als ob Sie nachlesen sollten, was Versprechen sind und warum sie im Allgemeinen verwendet werden. Sie sind nicht nur eckig und es gibt viel verfügbares Material
charlietfl

1
@charlietfl, guter Punkt, aber ich habe eine komplexe Antwort erwartet, die beides abdeckt: warum sie im Allgemeinen verwendet werden und wie man sie in Angular verwendet. Vielen Dank für Ihren Vorschlag
Maksym

Antworten:


401

Dies ist keine vollständige Antwort auf Ihre Frage, aber hoffentlich hilft dies Ihnen und anderen, wenn Sie versuchen, die Dokumentation zum $qDienst zu lesen . Ich habe eine Weile gebraucht, um es zu verstehen.

Lassen Sie uns AngularJS für einen Moment beiseite legen und nur die Facebook-API-Aufrufe betrachten. Beide API-Aufrufe verwenden einen Rückrufmechanismus , um den Anrufer zu benachrichtigen, wenn die Antwort von Facebook verfügbar ist:

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

Dies ist ein Standardmuster für die Verarbeitung asynchroner Vorgänge in JavaScript und anderen Sprachen.

Ein großes Problem mit diesem Muster tritt auf, wenn Sie eine Folge von asynchronen Operationen ausführen müssen, wobei jede aufeinanderfolgende Operation vom Ergebnis der vorherigen Operation abhängt. Das macht dieser Code:

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

Zuerst wird versucht, sich anzumelden, und erst nachdem überprüft wurde, ob die Anmeldung erfolgreich war, wird die Anforderung an die Graph-API gesendet.

Selbst in diesem Fall, in dem nur zwei Operationen miteinander verkettet werden, wird es langsam chaotisch. Die Methode askFacebookForAuthenticationakzeptiert einen Rückruf für Fehler und Erfolg. Was passiert jedoch, wenn dies FB.loginerfolgreich ist, aber FB.apifehlschlägt? Diese Methode ruft den successRückruf immer auf, unabhängig vom Ergebnis der FB.apiMethode.

Stellen Sie sich nun vor, Sie versuchen, eine robuste Folge von drei oder mehr asynchronen Vorgängen so zu codieren, dass Fehler bei jedem Schritt richtig behandelt werden und nach einigen Wochen für andere oder sogar für Sie lesbar sind. Möglich, aber es ist sehr einfach, diese Rückrufe einfach weiter zu verschachteln und dabei den Überblick über Fehler zu verlieren.

Lassen Sie uns nun die Facebook-API für einen Moment beiseite legen und nur die Angular Promises-API betrachten, die vom $qDienst implementiert wurde . Das von diesem Dienst implementierte Muster ist ein Versuch, die asynchrone Programmierung wieder in etwas zu verwandeln, das einer linearen Reihe einfacher Anweisungen ähnelt, mit der Fähigkeit, einen Fehler in jedem Schritt des Weges zu "werfen" und ihn am Ende zu behandeln, semantisch ähnlich dem vertrauter try/catchBlock.

Betrachten Sie dieses erfundene Beispiel. Angenommen, wir haben zwei Funktionen, wobei die zweite Funktion das Ergebnis der ersten verbraucht:

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

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

 secondFn(firstFn()); 

Stellen Sie sich nun vor, dass sowohl firstFn als auch secondFn lange dauern, sodass wir diese Sequenz asynchron verarbeiten möchten. Zuerst erstellen wir ein neues deferredObjekt, das eine Operationskette darstellt:

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

Die promiseEigenschaft repräsentiert das endgültige Ergebnis der Kette. Wenn Sie ein Versprechen unmittelbar nach dem Erstellen protokollieren, sehen Sie, dass es sich nur um ein leeres Objekt handelt ( {}). Noch nichts zu sehen, gehen Sie weiter.

Bisher ist unser Versprechen nur der Ausgangspunkt in der Kette. Fügen wir nun unsere beiden Operationen hinzu:

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

Die thenMethode fügt der Kette einen Schritt hinzu und gibt dann ein neues Versprechen zurück, das das endgültige Ergebnis der erweiterten Kette darstellt. Sie können beliebig viele Schritte hinzufügen.

Bisher haben wir unsere Funktionskette eingerichtet, aber es ist tatsächlich nichts passiert. Sie beginnen mit einem Aufruf deferred.resolveund geben den Anfangswert an, den Sie an den ersten tatsächlichen Schritt in der Kette übergeben möchten:

 deferred.resolve('initial value');

Und dann ... passiert immer noch nichts. Um sicherzustellen, dass Modelländerungen ordnungsgemäß beobachtet werden, ruft Angular den ersten Schritt in der Kette erst auf, wenn das nächste Mal $applyFolgendes aufgerufen wird:

 deferred.resolve('initial value');
 $rootScope.$apply();

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

Was ist also mit der Fehlerbehandlung? Bisher haben wir nur einen Erfolgshandler für jeden Schritt in der Kette angegeben. thenakzeptiert auch einen Fehlerhandler als optionales zweites Argument. Hier ist ein weiteres, längeres Beispiel für eine Versprechenskette, diesmal mit Fehlerbehandlung:

 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);

Wie Sie in diesem Beispiel sehen können, hat jeder Handler in der Kette die Möglichkeit, den Datenverkehr zum nächsten Fehlerhandler anstatt zum nächsten Erfolgshandler umzuleiten. In den meisten Fällen können Sie einen einzelnen Fehlerbehandler am Ende der Kette haben, aber Sie können auch Zwischenfehlerbehandler haben, die versuchen, eine Wiederherstellung durchzuführen.

Um schnell zu Ihren Beispielen (und Ihren Fragen) zurückzukehren, möchte ich nur sagen, dass sie zwei verschiedene Möglichkeiten darstellen, die rückruforientierte API von Facebook an Angulars Art der Beobachtung von Modelländerungen anzupassen. Das erste Beispiel umschließt den API-Aufruf mit einem Versprechen, das einem Bereich hinzugefügt werden kann und von Angulars Template-System verstanden wird. Der zweite Ansatz ist der Brute-Force-Ansatz, bei dem das Rückrufergebnis direkt auf den Bereich festgelegt und anschließend aufgerufen wird $scope.$digest(), um Angular auf die Änderung von einer externen Quelle aufmerksam zu machen.

Die beiden Beispiele sind nicht direkt vergleichbar, da im ersten der Anmeldeschritt fehlt. Im Allgemeinen ist es jedoch wünschenswert, Interaktionen mit solchen externen APIs in separaten Diensten zu kapseln und die Ergebnisse als Versprechen an Controller zu liefern. Auf diese Weise können Sie Ihre Controller von externen Belangen trennen und sie einfacher mit Mock-Services testen.


5
Ich denke, es ist eine großartige Antwort! Die Hauptsache für mich war, den allgemeinen Fall zu beschreiben, in dem das Versprechen wirklich aktuell ist. Ehrlich gesagt hatte ich auf ein weiteres echtes Beispiel gehofft (wie bei Facebook), aber das funktioniert auch, denke ich. Danke vielmals!
Maksym

2
Eine Alternative zur Verkettung mehrerer thenMethoden ist die Verwendung $q.all. Ein kurzes Tutorial dazu finden Sie hier .
Bogdan

2
$q.allist geeignet, wenn Sie warten müssen, bis mehrere unabhängige asynchrone Vorgänge abgeschlossen sind. Es ersetzt nicht die Verkettung, wenn jede Operation vom Ergebnis der vorherigen Operation abhängt.
Karlgold

1
Die Verkettung von dann wird hier kurz erklärt. Hat mir geholfen, es zu verstehen und sein volles Potenzial auszuschöpfen. Danke
Tushar Joshi

1
Tolle Antwort @karlgold! Ich habe eine Frage. Wenn Sie im letzten Codeausschnitt das return 'firstResult'Teil in ändern return $q.resolve('firstResult'), was ist der Unterschied?
Technophyle

9

Ich habe eine komplexe Antwort erwartet, die beides abdeckt: warum sie im Allgemeinen verwendet werden und wie man sie in Angular verwendet

Dies ist der Plunk für Winkelversprechen MVP (Minimum Viable Versprechen) : http://plnkr.co/edit/QBAB0usWXc96TnxqKhuA?p=preview

Quelle:

(für diejenigen, die zu faul sind, um auf die Links zu klicken)

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();

  });

(Ich weiß, dass es Ihr spezifisches Facebook-Beispiel nicht löst, aber ich finde folgende Ausschnitte nützlich)

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


Update 28. Februar 2014: Ab 1.2.0 werden Versprechen nicht mehr durch Vorlagen gelöst. http://www.benlesh.com/2013/02/angularjs-creating-service-with-http.html

(Plunker-Beispiel verwendet 1.1.5.)


afaik wir lieben so, weil wir faul sind
mkb

Dies hat mir geholfen, $ q zu verstehen, verzögert und verkettet. Dann Funktionsaufrufe, also danke.
Aliopi

1

Ein Zurückgestelltes repräsentiert das Ergebnis einer asynchronen Operation. Es stellt eine Schnittstelle bereit, die zum Signalisieren des Status und des Ergebnisses der von ihm dargestellten Operation verwendet werden kann. Es bietet auch eine Möglichkeit, die zugehörige Versprechen-Instanz abzurufen.

Ein Versprechen bietet eine Schnittstelle für die Interaktion mit dem zugehörigen Aufschub und ermöglicht so interessierten Parteien den Zugriff auf den Staat und das Ergebnis des aufgeschobenen Vorgangs.

Beim Erstellen eines Aufgeschobenen steht der Status aus und es wird kein Ergebnis angezeigt. Wenn wir den Zurückgestellten auflösen () oder ablehnen (), ändert sich sein Status in aufgelöst oder abgelehnt. Trotzdem können wir das damit verbundene Versprechen sofort nach dem Erstellen eines Aufschubs erhalten und sogar Interaktionen mit dem zukünftigen Ergebnis zuweisen. Diese Interaktionen treten erst auf, nachdem die Zurückstellung abgelehnt oder behoben wurde.


1

Verwenden Sie Versprechen innerhalb eines Controllers und stellen Sie sicher, dass die Daten verfügbar sind oder nicht

 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>

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.