Utilizando success / error / finally / catch con Promises en AngularJS

Estoy usando $http en AngularJs, y no estoy seguro de cómo usar la promesa devuelta y manejar los errores.

Tengo este 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 }); 

¿Es esta una buena manera de hacerlo, o hay una manera más fácil?

Las promesas son una abstracción sobre las declaraciones que nos permiten expressnos de forma sincrónica con el código asíncrono. Representan una ejecución de una tarea de una sola vez.

También proporcionan manejo de excepciones, al igual que el código normal, puede regresar de una promesa o puede lanzar.

Lo que querrías en el código sincrónico es:

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

La versión promisificada es muy similar:

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

Olvídate de usar el método de success y error .

Ambos métodos han quedado obsoletos en el angular 1.4. Básicamente, la razón detrás de la desaprobación es que no son encadenables , por así decirlo.

Con el siguiente ejemplo, intentaré demostrar lo que quiero decir acerca de que el success y el error no sean amigables para encadenarse . Supongamos que llamamos a una API que devuelve un objeto de usuario con una dirección:

Objeto de usuario:

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

Llamar a la 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'} }); }; 

¿Que pasó?

Debido a que el success y el error devuelven la promesa original , es decir, la devuelta por $http.get , el objeto pasado a la callback de then es el objeto de usuario completo, es decir, la misma entrada a la callback $http.get anterior.

Si hubiéramos encadenado dos then , esto habría sido menos confuso:

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

Creo que las respuestas anteriores son correctas, pero aquí hay otro ejemplo (solo un fyi, success () y error () están en desuso según la página principal de 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'); }); 

¿Qué tipo de granularidad estás buscando? Por lo general, puede salir adelante con:

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

Descubrí que “finalmente” y “atrapar” están mejor cuando encadenan múltiples promesas.

En el caso Angular $ http, la función success () y error () tendrá el objeto de respuesta desenvuelto, por lo que la firma de callback sería como $ http (…). Success (function (data, status, headers, config))

para entonces (), probablemente tratará con el objeto de respuesta sin formato. como publicado en el documento AngularJS $ http API

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

El último .catch (…) no será necesario a menos que haya un nuevo error en la cadena de promesa anterior.

Lo hago como Bradley Braithwaite sugiere en su 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; }); }]) 

Puntos clave:

  • La función de resolución se vincula a la función .then en nuestro controlador, es decir, todo está bien, por lo que podemos cumplir nuestra promesa y resolverla.

  • La función de rechazo se vincula a la función .catch en nuestro controlador, es decir, algo salió mal, por lo que no podemos cumplir nuestra promesa y necesitamos rechazarla.

Es bastante estable y seguro, y si tiene otras condiciones para rechazar la promesa, siempre puede filtrar sus datos en la función de éxito y llamar a deferred.reject(anotherReason) con el motivo del rechazo.

Como Ryan Vice sugirió en los comentarios , esto puede no ser visto como útil a menos que toquetee un poco la respuesta, por así decirlo.

Debido a que el success y el error están en desuso desde 1.4, tal vez sea mejor usar los métodos regulares de promesas y catch y transformar la respuesta dentro de esos métodos y devolver la promesa de esa respuesta transformada.

Estoy mostrando el mismo ejemplo con ambos enfoques y un tercer enfoque intermedio:

enfoque de success y error ( success y error devuelven la promesa de una respuesta HTTP, por lo que necesitamos la ayuda de $q para devolver una promesa de datos):

 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 y catch enfoque (esto es un poco más difícil de probar, debido al lanzamiento):

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

Sin embargo, hay una solución a medio camino (de esta manera puede evitar el throw y de todos modos probablemente necesite usar $q para burlarse del comportamiento prometedor en sus pruebas):

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

Cualquier tipo de comentarios o correcciones son bienvenidos.