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.