Utilisation de success / error / finally / catch avec des promesses dans AngularJS

112

J'utilise $httpdans AngularJs, et je ne sais pas comment utiliser la promesse retournée et gérer les erreurs.

J'ai ce code:

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

Est-ce une bonne façon de le faire ou existe-t-il un moyen plus simple?

Joël
la source

Réponses:

103

Les promesses sont une abstraction sur les déclarations qui nous permettent de nous exprimer de manière synchrone avec du code asynchrone. Ils représentent l'exécution d'une tâche ponctuelle.

Ils fournissent également la gestion des exceptions, tout comme le code normal, vous pouvez revenir d'une promesse ou vous pouvez lancer.

Ce que vous voudriez dans le code synchrone est:

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 version promis est très similaire:

$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
la source
4
Comment utiliseriez-vous success(), error()et finally()combiné avec catch()? Ou dois-je utiliserthen(successFunction, errorFunction).catch(exceotionHandling).then(cleanUp);
Joel
3
@Joel en général, vous ne voulez jamais utiliser successet error(préférez .thenet à la .catchplace, vous pouvez (et devriez) omettre errorFunctionl' .thenutilisation ac catchcomme dans mon code ci-dessus).
Benjamin Gruenbaum
@BenjaminGruenbaum pourriez-vous expliquer pourquoi vous suggérez d'éviter success/ error? De plus, mon Eclipse est folle quand il voit le .catch(, donc je l'utilise ["catch"](pour le moment. Comment apprivoiser Eclipse?
Giszmo
L'implémentation du module $ http d'Angular de la bibliothèque $ q utilise .success et .error au lieu de .then et .catch. Cependant, dans mes tests, j'ai pu accéder à toutes les propriétés de la promesse $ http en utilisant les promesses .then et .catch. Voir également la réponse de zd333.
Steve K
3
@SirBenBenji $ q n'a pas .successet .error, $ http renvoie une promesse $ q avec l'ajout des gestionnaires successet error- cependant, ces gestionnaires ne s'enchaînent pas et devraient généralement être évités si / lorsque cela est possible. En général, si vous avez des questions, il est préférable de les poser comme une nouvelle question et non comme un commentaire sur une ancienne.
Benjamin Gruenbaum
43

Oubliez l'utilisation successet la errorméthode.

Les deux méthodes ont été déconseillées dans angular 1.4. Fondamentalement, la raison de la dépréciation est qu'ils ne sont pas compatibles avec les chaînes , pour ainsi dire.

Avec l'exemple suivant, je vais essayer de montrer ce que je veux dire successet errorne pas être enchaîné . Supposons que nous appelions une API qui renvoie un objet utilisateur avec une adresse:

Objet utilisateur:

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

Appel à l'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'}
    });
};

Qu'est-il arrivé?

Car successet errorretourne la promesse d'origine , c'est-à-dire celle renvoyée par $http.get, l'objet passé au callback du thenest tout l' objet utilisateur , c'est-à-dire la même entrée au successcallback précédent .

Si nous en avions enchaîné deux then, cela aurait été moins déroutant:

$http.get('/user')
    .then(function (user) {
        return user.address;  
    })
    .then(function (obj) {  
        console.log(obj); // -> 'San Francisco'
    });
};
Michael P. Bazos
la source
1
Il convient également de noter cela successet neerror sont ajoutés qu'au retour immédiat de l' $httpappel (pas au prototype), donc si vous appelez une autre méthode de promesse entre eux (comme, vous appelez normalement return $http.get(url)enveloppé dans une bibliothèque de base, mais décidez plus tard de basculer un spinner dans l'appel de la bibliothèque avec return $http.get(url).finally(...)), vous n'aurez plus ces méthodes pratiques.
drzaus
35

Je pense que les réponses précédentes sont correctes, mais voici un autre exemple (juste un fyi, success () et error () sont obsolètes selon la page principale d' 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
la source
3
Enfin ne renvoie pas la réponse, à ma connaissance.
diplosaurus
11

Quel type de granularité recherchez-vous? Vous pouvez généralement vous en tirer avec:

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

J'ai trouvé que «enfin» et «attraper» sont mieux lotis pour enchaîner plusieurs promesses.

Justin
la source
1
Dans votre exemple, le gestionnaire d'erreurs ne gère que les erreurs $ http.
Benjamin Gruenbaum
1
Oui, je dois également gérer les exceptions dans les fonctions de succès / erreur. Et puis j'ai besoin d'une sorte de gestionnaire commun (où je peux définir des choses comme loading = false)
Joel
1
Vous avez une accolade au lieu de parenthèses fermant votre appel then ().
Paul McClean
1
Cela ne fonctionne pas sur les erreurs de réponse 404, ne fonctionne que sur la .catch()méthode
elporfirio
C'est la bonne réponse pour gérer les erreurs http renvoyées aux contrôleurs
Leon
5

Dans le cas de Angular $ http, les fonctions success () et error () auront l'objet de réponse déroulé, donc la signature de rappel serait comme $ http (...). Success (function (data, status, headers, config))

pour then (), vous vous occuperez probablement de l'objet de réponse brut. tel que publié dans le document d'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;
    });

Le dernier .catch (...) ne sera pas nécessaire à moins qu'il y ait une nouvelle erreur rejetée dans la chaîne de promesse précédente.

zd333
la source
2
Les méthodes Success / Error sont obsolètes.
OverMars
-3

Je le fais comme le suggère Bradley Braithwaite dans son 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;
            });
    }])

Points clés:

  • La fonction de résolution est liée à la fonction .then de notre contrôleur, c'est-à-dire que tout va bien, nous pouvons donc tenir notre promesse et la résoudre.

  • La fonction de rejet est liée à la fonction .catch de notre contrôleur, c'est-à-dire que quelque chose s'est mal passé, nous ne pouvons donc pas tenir notre promesse et devons la rejeter.

C'est assez stable et sûr et si vous avez d'autres conditions pour rejeter la promesse, vous pouvez toujours filtrer vos données dans la fonction de réussite et appeler deferred.reject(anotherReason)avec la raison du rejet.

Comme Ryan Vice l'a suggéré dans les commentaires , cela peut ne pas être considéré comme utile à moins que vous ne manipuliez un peu la réponse, pour ainsi dire.

Parce que successet errorsont obsolètes depuis la version 1.4, il est peut-être préférable d'utiliser les méthodes de promesse régulières thenetcatch et transformer la réponse dans ces méthodes et retourner la promesse de cette réponse transformée.

Je montre le même exemple avec les deux approches et une troisième approche intermédiaire:

successet errorapproche ( successet errorretourne une promesse de réponse HTTP, nous avons donc besoin de l'aide de $qpour renvoyer une promesse de données):

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

thenet catchapproche (c'est un peu plus difficile à tester, à cause du lancer):

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

Il existe cependant une solution à mi-chemin (de cette façon, vous pouvez éviter throwet de toute façon vous devrez probablement l'utiliser $qpour vous moquer du comportement de promesse dans vos tests):

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

Tout type de commentaire ou de correction est le bienvenu.

Horloger
la source
2
Pourquoi utiliseriez-vous $ q pour envelopper la promesse dans une promesse? Pourquoi ne pas simplement retourner la promesse renvoyée par $ http.get ()?
Ryan Vice
Parce que success()et error()ne renverrait pas une nouvelle promesse comme le then()fait. Avec $qnous faisons notre usine pour renvoyer une promesse de données au lieu d'une promesse d'une réponse HTTP.
Horloger
votre réponse est déroutante pour moi alors peut-être que je ne m'explique pas bien. à moins que vous ne manipuliez la réponse, vous pouvez simplement renvoyer la promesse que $ http renvoie. voir cet exemple je viens d'écrire: jsbin.com/belagan/edit?html,js,output
Ryan Vice
1
Je ne vois pas la valeur. Cela me semble inutile et je rejette les révisions de code sur mes projets qui utilisent cette approche, mais si vous en tirez de la valeur, vous devriez l'utiliser. J'ai également vu quelques promesses dans les articles sur les meilleures pratiques angulaires appelant un emballage inutile comme une odeur.
Ryan Vice
1
Il s'agit d'un anti-pattern différé . Lire Vous
manquez