Usando sucesso / erro / finalmente / catch com Promises em AngularJS

Estou usando $http no AngularJs e não tenho certeza sobre como usar a promise retornada e lidar com erros.

Eu tenho esse código:

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

Essa é uma boa maneira de fazer isso, ou há uma maneira mais fácil?

Promessas são uma abstração sobre declarações que nos permitem nos expressar de forma síncrona com o código asynchronous. Eles representam uma execução de uma tarefa única.

Eles também fornecem exception handling, assim como o código normal, você pode retornar de uma promise ou você pode jogar.

O que você deseja em código síncrono é:

 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. } 

A versão promisificada é muito semelhante:

 $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. }); 

Esqueça o uso do método de success e error .

Ambos os methods foram preteridos em angular 1.4. Basicamente, a razão por trás da depreciação é que eles não são amigáveis , por assim dizer.

Com o exemplo a seguir, tentarei demonstrar o que quero dizer com success e error não sendo amigável para cadeias . Suponha que chamamos uma API que retorna um object de usuário com um endereço:

Objeto do usuário:

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

Ligue para a 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'} }); }; 

O que aconteceu?

Como o success e o error retornam a promise original , ou seja, a retornada por $http.get , o object passado para o retorno de chamada é o object de usuário inteiro, ou seja, a mesma input para o retorno de chamada de success anterior.

Se tivéssemos acorrentado dois then , isso teria sido menos confuso:

 $http.get('/user') .then(function (user) { return user.address; }) .then(function (obj) { console.log(obj); // -> 'San Francisco' }); }; 

Eu acho que as respostas anteriores estão corretas, mas aqui está outro exemplo (apenas um fyi, success () e error () estão obsoletos de acordo com a página principal do 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'); }); 

Que tipo de granularidade você está procurando? Você normalmente pode conviver com:

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

Eu descobri que “finalmente” e “catch” são melhores quando encadeando várias promises.

No caso de $ http Angular, a function success () e error () terá o object de resposta sido desembrulhado, portanto a assinatura de retorno de chamada seria como $ http (…). Success (function (data, status, headers, config))

para then (), você provavelmente lidará com o object de resposta bruto. como postado no documento da 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; }); 

O último .catch (…) não precisará, a menos que haja um novo erro descartado na cadeia de promises anterior.

Eu faço como Bradley Braithwaite sugere em seu blog :

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

Pontos chave:

  • A function resolve está ligada à function .then em nosso controller, ou seja, tudo está bem, então podemos manter nossa promise e resolvê-la.

  • A function de rejeição está vinculada à function .catch em nosso controlador, ou seja, algo deu errado, então não podemos cumprir nossa promise e precisamos rejeitá-la.

É bastante estável e seguro e se você tiver outras condições para rejeitar a promise, você sempre pode filtrar seus dados na function success e chamar deferred.reject(anotherReason) com o motivo da rejeição.

Como Ryan Vice sugeriu nos comentários , isso pode não ser visto como útil, a menos que você mexa um pouco com a resposta, por assim dizer.

Como o success e o error estão obsoletos desde a versão 1.4, talvez seja melhor usar os methods de promise regulares e catch e transformar a resposta dentro desses methods e retornar a promise dessa resposta transformada.

Eu estou mostrando o mesmo exemplo com ambas as abordagens e uma terceira abordagem intermediária:

abordagem de success e error ( success e error retornam a promise de uma resposta HTTP, então precisamos da ajuda de $q para retornar uma promise de dados):

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

then e catch aproximação (isto é um pouco mais difícil de testar, por causa do lançamento):

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

Existe uma solução intermediária (desta forma você pode evitar o throw e, de qualquer maneira, você provavelmente precisará usar $q para simular o comportamento prometido em seus testes):

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

Qualquer tipo de comentários ou correções são bem-vindos.