Dies ist keine vollständige Antwort auf Ihre Frage, aber hoffentlich hilft dies Ihnen und anderen, wenn Sie versuchen, die Dokumentation zum $q
Dienst 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 askFacebookForAuthentication
akzeptiert einen Rückruf für Fehler und Erfolg. Was passiert jedoch, wenn dies FB.login
erfolgreich ist, aber FB.api
fehlschlägt? Diese Methode ruft den success
Rückruf immer auf, unabhängig vom Ergebnis der FB.api
Methode.
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 $q
Dienst 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/catch
Block.
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 deferred
Objekt, das eine Operationskette darstellt:
var deferred = $q.defer();
var promise = deferred.promise;
Die promise
Eigenschaft 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 then
Methode 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.resolve
und 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 $apply
Folgendes 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. then
akzeptiert 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.