Usando éxito / error / finalmente / captura con promesas en AngularJS

112

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

Tengo este codigo:

$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 forma de hacerlo o hay una forma más sencilla?

Joel
fuente

Respuestas:

103

Las promesas son una abstracción de las declaraciones que nos permiten expresarnos sincrónicamente con código asincrónico. Representan la ejecución de una tarea única.

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

Lo que querría en el código síncrono 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 prometida 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.
});
Benjamin Gruenbaum
fuente
4
¿Cómo utilizaría success(), error()y finally()combinado con catch()? O tengo que usarthen(successFunction, errorFunction).catch(exceotionHandling).then(cleanUp);
Joel
3
@Joel en general, no desea usar nunca successy error(prefiere .theny en su .catchlugar, puede (y debe) omitir el errorFunctiondel .thenuso ac catchcomo en mi código anterior).
Benjamin Gruenbaum
@BenjaminGruenbaum, ¿podría explicar por qué sugiere evitar success/ error? Además, mi Eclipse se vuelve loco cuando ve el .catch(, así que lo uso ["catch"](por ahora. ¿Cómo puedo domesticar a Eclipse?
Giszmo
La implementación del módulo $ http de Angular de la biblioteca $ q usa .success y .error en lugar de .then y .catch. Sin embargo, en mis pruebas pude acceder a todas las propiedades de la promesa $ http al usar las promesas .then y .catch. También vea la respuesta de zd333.
Steve K
3
@SirBenBenji $ q no tiene .successy .error, $ http devuelve una promesa $ q con la adición de los controladores successy error; sin embargo, estos controladores no se encadenan y generalmente deben evitarse si es posible. En general, si tiene preguntas, es mejor plantearlas como una pregunta nueva y no como un comentario sobre una anterior.
Benjamin Gruenbaum
43

Olvidarse de usar successy errormétodo.

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

Con el siguiente ejemplo, voy a tratar de demostrar lo que quiero decir acerca successy errorno siendo chainable ambiente . 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ó?

Porque successy errordevuelve la promesa original , es decir, la devuelta por $http.get, el objeto pasado a la devolución de llamada del thenes el objeto de usuario completo , es decir, la misma entrada a la successdevolución de llamada 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'
    });
};
Michael P. Bazos
fuente
1
También vale la pena señalar que successy errorson sólo añadidas a la devolución inmediata de la $httpllamada (no el prototipo), por lo que si se llama a otro método promesa entre ellos (como, normalmente llamamos return $http.get(url)envuelto en una biblioteca de la base, pero más tarde decide cambiar una ruleta en la llamada a la biblioteca con return $http.get(url).finally(...)), entonces ya no tendrá esos métodos convenientes.
drzaus
35

Creo que las respuestas anteriores son correctas, pero aquí hay otro ejemplo (solo un fyi, success () y error () están en desuso de acuerdo con 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');
    });
grepit
fuente
3
Finalmente no devuelve la respuesta, que yo sepa.
diplosaurus
11

¿Qué tipo de granularidad está buscando? Por lo general, puede arreglárselas con:

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

Descubrí que "finalmente" y "captura" están mejor cuando se encadenan múltiples promesas.

justin
fuente
1
En su ejemplo, el controlador de errores solo maneja errores $ http.
Benjamin Gruenbaum
1
Sí, todavía necesito manejar excepciones en las funciones de éxito / error también. Y luego necesito algún tipo de controlador común (donde pueda establecer cosas como loading = false)
Joel
1
Tiene una llave en lugar de un paréntesis que cierra su llamada then ().
Paul McClean
1
Esto no funciona con errores de respuesta 404, solo funciona con el .catch()método
elporfirio
Esta es la respuesta correcta para manejar los errores http devueltos a los controladores
Leon
5

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

para then (), probablemente tratará con el objeto de respuesta en bruto. como publicado en el documento 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;
    });

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

zd333
fuente
2
Los métodos de éxito / error están obsoletos.
OverMars
-3

Lo hago como sugiere Bradley Braithwaite 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 enlaza con la función .then en nuestro controlador, es decir, todo está bien, por lo que podemos mantener nuestra promesa y resolverla.

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

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

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

Porque successy errorestán en desuso desde 1.4, tal vez sea mejor usar los métodos de promesa regulares thenycatch y transformar la respuesta dentro de esos métodos y devolver la promesa de que la respuesta transformada.

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

successy errorenfoque ( successy errordevolver una promesa de una respuesta HTTP, por lo que necesitamos la ayuda de $qpara 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;
};

theny catchenfoque (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 mitad de camino (de esta manera puede evitar el throwy de todos modos probablemente necesitará usar $qpara simular el comportamiento de la promesa 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.

Relojero
fuente
2
¿Por qué usarías $ q para envolver la promesa en una promesa? ¿Por qué no devolver la promesa que devuelve $ http.get ()?
Ryan Vice
Porque success()y error()no devolvería una nueva promesa como lo then()hace. Con $qhacemos que nuestra fábrica devuelva una promesa de datos en lugar de una promesa de respuesta HTTP.
Relojero
Tu respuesta es confusa para mí, así que tal vez no me estoy explicando bien. a menos que esté manipulando la respuesta, simplemente puede devolver la promesa de que $ http devuelve. vea este ejemplo que acabo de escribir: jsbin.com/belagan/edit?html,js,output
Ryan Vice
1
No veo el valor. Me parece innecesario y rechazo las revisiones de código en mis proyectos que usan este enfoque, pero si obtiene valor de él, debería usarlo. También he visto algunas promesas en artículos de mejores prácticas angulares que mencionan el envoltorio innecesario como un olor.
Ryan Vice
1
Este es un anti-patrón diferido . Leer Estás
perdiendo