Używanie sukcesu / błędu / w końcu / złap z obietnicami w AngularJS

112

Używam $httpw AngularJs i nie jestem pewien, jak używać zwróconej obietnicy i radzić sobie z błędami.

Mam ten kod:

$http
    .get(url)
    .success(function(data) {
        // Handle data
    })
    .error(function(data, status) {
        // Handle HTTP error
    })
    .finally(function() {
        // Execute logic independent of success/error
    })
    .catch(function(error) {
        // Catch and handle exceptions from success/error/finally functions
    });

Czy to dobry sposób, czy jest łatwiejszy sposób?

Joel
źródło

Odpowiedzi:

103

Obietnice są abstrakcją w stosunku do instrukcji, które pozwalają nam wyrażać siebie synchronicznie za pomocą kodu asynchronicznego. Reprezentują wykonanie jednorazowego zadania.

Zapewniają również obsługę wyjątków, podobnie jak normalny kod, możesz powrócić z obietnicy lub możesz rzucić.

To, czego chciałbyś w kodzie synchronicznym, to:

try{
  try{
      var res = $http.getSync("url");
      res = someProcessingOf(res);
  } catch (e) {
      console.log("Got an error!",e);
      throw e; // rethrow to not marked as handled
  }
  // do more stuff with res
} catch (e){
     // handle errors in processing or in error.
}

Obiecana wersja jest bardzo podobna:

$http.get("url").
then(someProcessingOf).
catch(function(e){
   console.log("got an error in initial processing",e);
   throw e; // rethrow to not marked as handled, 
            // in $q it's better to `return $q.reject(e)` here
}).then(function(res){
    // do more stuff
}).catch(function(e){
    // handle errors in processing or in error.
});
Benjamin Gruenbaum
źródło
4
W jaki sposób można wykorzystać success(), error()a finally()w połączeniu z catch()? Czy też muszę użyćthen(successFunction, errorFunction).catch(exceotionHandling).then(cleanUp);
Joel
3
@Joel ogólnie rzecz biorąc, nie chcesz nigdy używać successi error(wolisz .theni .catchzamiast tego możesz (i powinieneś) pominąć errorFunctionw .thenużyciu ac, catchjak w moim kodzie powyżej).
Benjamin Gruenbaum
@BenjaminGruenbaum czy mógłbyś wyjaśnić, dlaczego sugerujesz unikać success/ error? Również moje Eclipse wpada w amok, kiedy widzi .catch(, więc ["catch"](na razie używam . Jak mogę oswoić Eclipse?
Giszmo
Implementacja modułu $ http Angulara w bibliotece $ q używa .success i .error zamiast .then i .catch. Jednak w moich testach mogłem uzyskać dostęp do wszystkich właściwości obietnicy $ http, używając obietnic .then i .catch. Zobacz także odpowiedź zd333.
Steve K
3
@SirBenBenji $ q nie ma .successi .error, $ http zwraca $ q obietnicę z dodatkiem tych successi errorkoparki - jednak te koparki nie łańcuch i generalnie należy unikać, jeśli / kiedy to możliwe. Ogólnie - jeśli masz pytania, najlepiej zadaj je jako nowe pytanie, a nie jako komentarz do starego.
Benjamin Gruenbaum
43

Zapomnij o używaniu successi errormetodzie.

Obie metody zostały wycofane w wersji kątowej 1.4. Zasadniczo powodem wycofania jest to, że nie są one przyjazne dla łańcuchów , że tak powiem.

Dzięki poniższym przykładzie postaram się wykazać, co mam na myśli około successi errorjest nie chainable w obsłudze . Załóżmy, że wywołujemy API, które zwraca obiekt użytkownika o adresie:

Obiekt użytkownika:

{name: 'Igor', address: 'San Francisco'}

Zadzwoń do API:

$http.get('/user')
    .success(function (user) {
        return user.address;   <---  
    })                            |  // you might expect that 'obj' is equal to the
    .then(function (obj) {   ------  // address of the user, but it is NOT

        console.log(obj); // -> {name: 'Igor', address: 'San Francisco'}
    });
};

Co się stało?

Ponieważ successi errorzwraca pierwotną obietnicę , tj. Tę zwróconą przez $http.get, obiekt przekazany do wywołania zwrotnego thenjest całym obiektem użytkownika , to znaczy tym samym wejściem do poprzedniego successwywołania zwrotnego.

Gdybyśmy połączyli dwa łańcuchy then, byłoby to mniej zagmatwane:

$http.get('/user')
    .then(function (user) {
        return user.address;  
    })
    .then(function (obj) {  
        console.log(obj); // -> 'San Francisco'
    });
};
Michael P. Bazos
źródło
1
Warto również zauważyć, że successi errordodawane tylko do natychmiastowego powrotu do $httprozmowy (nie prototyp), więc jeśli zadzwonisz inny sposób obietnica między nimi (jak, zwykle nazywamy return $http.get(url)zawinięte w bibliotece bazowej, ale później zdecydujesz się przełączyć pokrętło w wywołanie biblioteki z return $http.get(url).finally(...)), nie będziesz już mieć tych wygodnych metod.
drzaus
35

Myślę, że poprzednie odpowiedzi są poprawne, ale oto inny przykład (tylko fyi, success () i error () są przestarzałe zgodnie ze stroną główną AngularJS :

$http
    .get('http://someendpoint/maybe/returns/JSON')
    .then(function(response) {
        return response.data;
    }).catch(function(e) {
        console.log('Error: ', e);
        throw e;
    }).finally(function() {
        console.log('This finally block');
    });
grepit
źródło
3
W końcu nie zwraca odpowiedzi, o ile wiem.
diplosaurus
11

Jakiego rodzaju szczegółowości szukasz? Zwykle możesz sobie poradzić z:

$http.get(url).then(
  //success function
  function(results) {
    //do something w/results.data
  },
  //error function
  function(err) {
    //handle error
  }
);

Odkryłem, że „nareszcie” i „złapanie” są lepsze, gdy wiąże się wiele obietnic.

Justin
źródło
1
W twoim przykładzie program obsługi błędów obsługuje tylko błędy $ http.
Benjamin Gruenbaum
1
Tak, nadal muszę obsługiwać wyjątki w funkcjach sukcesu / błędu. A potem potrzebuję jakiegoś wspólnego przewodnika (gdzie mogę ustawić takie rzeczy loading = false)
Joel
1
Masz nawias klamrowy zamiast nawiasów zamykających wywołanie then ().
Paul McClean,
1
To nie działa w przypadku błędów odpowiedzi 404, działa tylko na .catch()metodzie
elporfirio
To jest poprawna odpowiedź na obsługę błędów http zwróconych do kontrolerów
Leon
5

W przypadku Angular $ http funkcje success () i error () będą miały obiekt odpowiedzi rozpakowany, więc sygnatura wywołania zwrotnego będzie wyglądać następująco: $ http (...). Success (function (data, status, headers, config))

dla then () prawdopodobnie zajmiesz się surowym obiektem odpowiedzi. takie jak opublikowane w dokumencie API AngularJS $ http

$http({
        url: $scope.url,
        method: $scope.method,
        cache: $templateCache
    })
    .success(function(data, status) {
        $scope.status = status;
        $scope.data = data;
    })
    .error(function(data, status) {
        $scope.data = data || 'Request failed';
        $scope.status = status;
    });

Ostatni .catch (...) nie będzie potrzebny, chyba że w poprzednim łańcuchu obietnic pojawi się nowy błąd.

zd333
źródło
2
Metody sukcesu / błędu są przestarzałe.
OverMars
-3

Robię to tak, jak sugeruje Bradley Braithwaite na swoim blogu :

app
    .factory('searchService', ['$q', '$http', function($q, $http) {
        var service = {};

        service.search = function search(query) {
            // We make use of Angular's $q library to create the deferred instance
            var deferred = $q.defer();

            $http
                .get('http://localhost/v1?=q' + query)
                .success(function(data) {
                    // The promise is resolved once the HTTP call is successful.
                    deferred.resolve(data);
                })
                .error(function(reason) {
                    // The promise is rejected if there is an error with the HTTP call.
                    deferred.reject(reason);
                });

            // The promise is returned to the caller
            return deferred.promise;
        };

        return service;
    }])
    .controller('SearchController', ['$scope', 'searchService', function($scope, searchService) {
        // The search service returns a promise API
        searchService
            .search($scope.query)
            .then(function(data) {
                // This is set when the promise is resolved.
                $scope.results = data;
            })
            .catch(function(reason) {
                // This is set in the event of an error.
                $scope.error = 'There has been an error: ' + reason;
            });
    }])

Kluczowe punkty:

  • Funkcja rozwiązywania łączy się z funkcją .then w naszym kontrolerze, czyli wszystko jest w porządku, więc możemy dotrzymać obietnicy i rozwiązać ją.

  • Funkcja odrzucania łączy się z funkcją .catch w naszym kontrolerze, czyli coś poszło nie tak, więc nie możemy dotrzymać obietnicy i musimy ją odrzucić.

Jest dość stabilny i bezpieczny, a jeśli masz inne warunki, aby odrzucić obietnicę, zawsze możesz filtrować swoje dane w funkcji sukcesu i dzwonić deferred.reject(anotherReason)z przyczyną odrzucenia.

Jak zasugerował Ryan Vice w komentarzach , może to nie być postrzegane jako przydatne, chyba że będziesz trochę bawić się odpowiedzią, że tak powiem.

Ponieważ successi errorsą przestarzałe od wersji 1.4, być może lepiej jest użyć zwykłych metod obietnicy thenicatch i przekształcenia odpowiedzi w tych metodach i powrócić obietnicę tej przekształconej odpowiedzi.

Pokazuję ten sam przykład z obydwoma podejściami i trzecim podejściem pośrednim:

successi errorpodejdź ( successi errorzwróć obietnicę odpowiedzi HTTP, więc potrzebujemy pomocy, $qaby zwrócić obietnicę danych):

function search(query) {
  // We make use of Angular's $q library to create the deferred instance
  var deferred = $q.defer();

  $http.get('http://localhost/v1?=q' + query)
  .success(function(data,status) {
    // The promise is resolved once the HTTP call is successful.
    deferred.resolve(data);              
  })

  .error(function(reason,status) {
    // The promise is rejected if there is an error with the HTTP call.
    if(reason.error){
      deferred.reject({text:reason.error, status:status});
    }else{
      //if we don't get any answers the proxy/api will probably be down
      deferred.reject({text:'whatever', status:500});
    }
  });

  // The promise is returned to the caller
  return deferred.promise;
};

theni catchpodejście (jest to trochę trudniejsze do przetestowania ze względu na rzut):

function search(query) {

  var promise=$http.get('http://localhost/v1?=q' + query)

  .then(function (response) {
    // The promise is resolved once the HTTP call is successful.
    return response.data;
  },function(reason) {
    // The promise is rejected if there is an error with the HTTP call.
    if(reason.statusText){
      throw reason;
    }else{
      //if we don't get any answers the proxy/api will probably be down
      throw {statusText:'Call error', status:500};
    }

  });

  return promise;
}

Istnieje jednak rozwiązanie połowiczne (w ten sposób możesz uniknąć, throwa mimo to prawdopodobnie będziesz musiał użyć, $qaby kpić z obiecującego zachowania w swoich testach):

function search(query) {
  // We make use of Angular's $q library to create the deferred instance
  var deferred = $q.defer();

  $http.get('http://localhost/v1?=q' + query)

  .then(function (response) {
    // The promise is resolved once the HTTP call is successful.
    deferred.resolve(response.data);
  },function(reason) {
    // The promise is rejected if there is an error with the HTTP call.
    if(reason.statusText){
      deferred.reject(reason);
    }else{
      //if we don't get any answers the proxy/api will probably be down
      deferred.reject({statusText:'Call error', status:500});
    }

  });

  // The promise is returned to the caller
  return deferred.promise;
}

Wszelkie uwagi i poprawki są mile widziane.

Zegarmistrz
źródło
2
Dlaczego miałbyś używać $ q do zawijania obietnicy w obietnicę? Dlaczego po prostu nie zwrócić obietnicy zwróconej przez $ http.get ()?
Ryan Vice
Ponieważ success()i error()nie zwróci nowej obietnicy, jak to then()robi. Dzięki temu $qnasza fabryka zwraca obietnicę danych zamiast obietnicy odpowiedzi HTTP.
Zegarmistrz
twoja odpowiedź jest dla mnie myląca, więc może nie tłumaczę się dobrze. chyba że manipulujesz odpowiedzią, możesz po prostu zwrócić obietnicę, że $ http zwraca. zobacz ten przykład, który właśnie napisałem: jsbin.com/belagan/edit?html,js,output
Ryan Vice
1
Nie widzę wartości. Wydaje mi się to niepotrzebne i odrzucam recenzje kodu w moich projektach, które wykorzystują to podejście, ale jeśli czerpiesz z tego wartość, powinieneś z niego skorzystać. Widziałem również kilka obietnic w artykułach dotyczących najlepszych praktyk dotyczących kątów, w których niepotrzebne zawijanie było zapachem.
Ryan Vice
1
To jest odroczony anty-wzór . Przeczytaj , że nie
spełniasz