To nie będzie pełna odpowiedź na twoje pytanie, ale miejmy nadzieję, że pomoże to tobie i innym podczas próby zapoznania się z dokumentacją dotyczącą $q
usługi. Zajęło mi trochę czasu, zanim to zrozumiałem.
Odłóżmy na chwilę AngularJS i rozważmy wywołania API Facebooka. Oba wywołania API wykorzystują mechanizm wywołania zwrotnego , aby powiadomić dzwoniącego o dostępności odpowiedzi z Facebooka:
facebook.FB.api('/' + item, function (result) {
if (result.error) {
// handle error
} else {
// handle success
}
});
// program continues while request is pending
...
Jest to standardowy wzorzec do obsługi operacji asynchronicznych w JavaScript i innych językach.
Jeden duży problem z tym wzorcem pojawia się, gdy trzeba wykonać sekwencję operacji asynchronicznych, w których każda kolejna operacja zależy od wyniku poprzedniej operacji. To właśnie robi ten kod:
FB.login(function(response) {
if (response.authResponse) {
FB.api('/me', success);
} else {
fail('User cancelled login or did not fully authorize.');
}
});
Najpierw próbuje się zalogować, a dopiero po sprawdzeniu, czy logowanie się powiodło, wysyła żądanie do Graph API.
Nawet w tym przypadku, który polega tylko na połączeniu dwóch operacji w łańcuch, sytuacja zaczyna się komplikować. Metoda askFacebookForAuthentication
akceptuje wywołanie zwrotne w przypadku niepowodzenia i sukcesu, ale co się dzieje, gdy się FB.login
powiedzie, ale FB.api
zawiedzie? Ta metoda zawsze wywołuje success
wywołanie zwrotne niezależnie od wyniku FB.api
metody.
Teraz wyobraź sobie, że próbujesz zakodować solidną sekwencję trzech lub więcej operacji asynchronicznych w sposób, który poprawnie obsługuje błędy na każdym kroku i będzie czytelny dla każdego, a nawet dla Ciebie po kilku tygodniach. Możliwe, ale bardzo łatwo jest po prostu zagnieżdżać te wywołania zwrotne i zgubić błędy po drodze.
Teraz odłóżmy na chwilę na bok API Facebooka i po prostu rozważmy API Angular Promises zaimplementowane przez $q
usługę. Wzorzec zaimplementowany przez tę usługę jest próbą przekształcenia programowania asynchronicznego z powrotem w coś przypominającego liniową serię prostych instrukcji, z możliwością `` rzucenia '' błędu na dowolnym etapie i obsłużenia go na końcu, semantycznie podobnym do znajomy try/catch
blok.
Rozważmy ten wymyślony przykład. Powiedzmy, że mamy dwie funkcje, gdzie druga funkcja zużywa wynik pierwszej:
var firstFn = function(param) {
// do something with param
return 'firstResult';
};
var secondFn = function(param) {
// do something with param
return 'secondResult';
};
secondFn(firstFn());
Teraz wyobraź sobie, że pierwszeFn i secondFn zajmują dużo czasu, więc chcemy przetwarzać tę sekwencję asynchronicznie. Najpierw tworzymy nowy deferred
obiekt, który reprezentuje łańcuch operacji:
var deferred = $q.defer();
var promise = deferred.promise;
promise
Właściwość reprezentuje ewentualny wynik łańcuchu. Jeśli zarejestrujesz obietnicę natychmiast po jej utworzeniu, zobaczysz, że jest to po prostu pusty obiekt ( {}
). Nie ma jeszcze nic do oglądania, przejdź od razu.
Jak dotąd nasza obietnica reprezentuje jedynie punkt wyjścia w łańcuchu. Teraz dodajmy nasze dwie operacje:
promise = promise.then(firstFn).then(secondFn);
then
Metoda dodaje krok do łańcucha, a następnie zwraca nową obietnicę reprezentujący ewentualny wynik rozszerzonego łańcucha. Możesz dodać tyle kroków, ile chcesz.
Do tej pory skonfigurowaliśmy nasz łańcuch funkcji, ale tak naprawdę nic się nie wydarzyło. Rozpoczynasz od wywołania deferred.resolve
i określenia wartości początkowej, którą chcesz przekazać do pierwszego rzeczywistego kroku w łańcuchu:
deferred.resolve('initial value');
A potem ... nadal nic się nie dzieje. Aby upewnić się, że zmiany modelu są prawidłowo obserwowane, Angular nie wywołuje pierwszego kroku w łańcuchu, dopóki nie $apply
zostanie wywołany następny raz :
deferred.resolve('initial value');
$rootScope.$apply();
// or
$rootScope.$apply(function() {
deferred.resolve('initial value');
});
A co z obsługą błędów? Do tej pory określiliśmy tylko procedurę obsługi sukcesu na każdym etapie łańcucha. then
akceptuje również procedurę obsługi błędów jako opcjonalny drugi argument. Oto kolejny, dłuższy przykład łańcucha obietnic, tym razem z obsługą błędów:
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);
Jak widać w tym przykładzie, każdy program obsługi w łańcuchu ma możliwość skierowania ruchu do następnego programu obsługi błędów zamiast do następnego programu obsługi sukcesu . W większości przypadków na końcu łańcucha może znajdować się jedna procedura obsługi błędów, ale można również mieć pośrednie procedury obsługi błędów, które próbują odzyskać.
Aby szybko wrócić do twoich przykładów (i twoich pytań), powiem tylko, że reprezentują one dwa różne sposoby dostosowania API zorientowanego na wywołania zwrotne Facebooka do sposobu obserwowania zmian modelu przez Angular. Pierwszy przykład opakowuje wywołanie API w obietnicę, którą można dodać do zakresu i która jest rozumiana przez system szablonów Angulara. Drugi przyjmuje bardziej brutalne podejście polegające na ustawianiu wyniku wywołania zwrotnego bezpośrednio w zakresie, a następnie wywoływaniu, $scope.$digest()
aby Angular był świadomy zmiany z zewnętrznego źródła.
Te dwa przykłady nie są bezpośrednio porównywalne, ponieważ w pierwszym brakuje etapu logowania. Jednak ogólnie pożądane jest hermetyzowanie interakcji z zewnętrznymi interfejsami API, takimi jak ten, w oddzielnych usługach i dostarczanie wyników kontrolerom zgodnie z obietnicą. W ten sposób możesz oddzielić kontrolery od problemów zewnętrznych i łatwiej je przetestować za pomocą usług pozorowanych.