Verwenden von success / error / finally / catch mit Promises in AngularJS

112

Ich verwende $httpin AngularJs und bin mir nicht sicher, wie ich das zurückgegebene Versprechen verwenden und mit Fehlern umgehen soll.

Ich habe diesen 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
    });

Ist das ein guter Weg, oder gibt es einen einfacheren Weg?

Joel
quelle

Antworten:

103

Versprechen sind eine Abstraktion über Aussagen, die es uns ermöglichen, uns synchron mit asynchronem Code auszudrücken. Sie repräsentieren die Ausführung einer einmaligen Aufgabe.

Sie bieten auch Ausnahmebehandlung, genau wie normaler Code, Sie können von einem Versprechen zurückkehren oder Sie können werfen.

Was Sie im synchronen Code wollen, ist:

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

Die versprochene Version ist sehr ähnlich:

$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
quelle
4
Wie würden Sie verwenden success(), error()und finally()kombiniert mit catch()? Oder muss ich verwendenthen(successFunction, errorFunction).catch(exceotionHandling).then(cleanUp);
Joel
3
@Joel im Allgemeinen möchten Sie nie verwenden successund error(bevorzugen .thenund .catchstattdessen können (und sollten) Sie errorFunctiondie .thenVerwendung von ac catchwie in meinem Code oben weglassen ).
Benjamin Gruenbaum
@BenjaminGruenbaum Könnten Sie näher erläutern, warum Sie vorschlagen, success/ zu vermeiden error? Auch meine Eclipse läuft Amok, wenn sie das sieht .catch(, also benutze ich es vorerst ["catch"](. Wie kann ich Eclipse zähmen?
Giszmo
Angulars $ http-Modulimplementierung der $ q-Bibliothek verwendet .success und .error anstelle von .then und .catch. In meinen Tests konnte ich jedoch auf alle Eigenschaften des $ http-Versprechens zugreifen, wenn ich .then- und .catch-Versprechen verwendete. Siehe auch die Antwort von zd333.
Steve K
3
@SirBenBenji $ q hat kein .successund .error$ http gibt ein $ q-Versprechen mit dem Zusatz der successund error-Handler zurück. Diese Handler sind jedoch nicht verkettet und sollten generell vermieden werden, wenn / wenn möglich. Im Allgemeinen - wenn Sie Fragen haben, ist es am besten, sie als neue Frage und nicht als Kommentar zu einer alten Frage zu stellen.
Benjamin Gruenbaum
43

Vergessen Sie die Verwendung successund errorMethode.

Beide Methoden sind in Winkel 1.4 veraltet. Grundsätzlich liegt der Grund für die Abwertung darin, dass sie sozusagen nicht verkettenfreundlich sind .

Mit dem folgenden Beispiel werde ich versuchen zu demonstrieren, was ich meine successund errornicht kettenfreundlich zu sein . Angenommen, wir rufen eine API auf, die ein Benutzerobjekt mit einer Adresse zurückgibt:

Benutzerobjekt:

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

Aufruf der 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'}
    });
};

Was ist passiert?

Weil successund errordas ursprüngliche Versprechen zurückgeben , dh das von zurückgegebene $http.get, ist das an den Rückruf von übergebene Objekt thendas gesamte Benutzerobjekt , dh die gleiche Eingabe für den vorhergehenden successRückruf.

Wenn wir zwei verkettet hätten then, wäre dies weniger verwirrend gewesen:

$http.get('/user')
    .then(function (user) {
        return user.address;  
    })
    .then(function (obj) {  
        console.log(obj); // -> 'San Francisco'
    });
};
Michael P. Bazos
quelle
1
Auch erwähnenswert , dass successund errorsind nur für die sofortige Rückgabe hinzugefügt des $httpAnrufs (nicht der Prototyp), so dass , wenn Sie ein anderes Versprechen Verfahren zwischen ihnen nennen (wie Sie in der Regel rufen Sie return $http.get(url)in einer Basisbibliothek eingewickelt, aber später entscheiden , einen Spinner wechseln in Wenn die Bibliothek mit return $http.get(url).finally(...)) aufruft, stehen Ihnen diese praktischen Methoden nicht mehr zur Verfügung.
Drzaus
35

Ich denke, die vorherigen Antworten sind korrekt, aber hier ist ein weiteres Beispiel (nur zu Ihrer Information, Erfolg () und Fehler () sind laut AngularJS- Hauptseite veraltet :

$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
quelle
3
Schließlich gibt die Antwort meines Wissens nicht zurück.
Diplosaurus
11

Welche Art von Granularität suchen Sie? Sie können in der Regel auskommen mit:

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

Ich habe festgestellt, dass "endlich" und "fangen" besser dran sind, wenn mehrere Versprechen verkettet werden.

Justin
quelle
1
In Ihrem Beispiel behandelt der Fehlerbehandler nur $ http-Fehler.
Benjamin Gruenbaum
1
Ja, ich muss auch noch Ausnahmen in den Erfolgs- / Fehlerfunktionen behandeln. Und dann brauche ich eine Art gemeinsamen Handler (wo ich Dinge einstellen kann wie loading = false)
Joel
1
Sie haben eine Klammer anstelle einer Klammer, die Ihren then () -Aufruf abschließt.
Paul McClean
1
Dies funktioniert nicht bei 404 Antwortfehlern, .catch()
sondern
Dies ist die richtige Antwort für die Behandlung von http-Fehlern, die an Controller zurückgegeben wurden
Leon
5

Im Fall von Angular $ http wurde für die Funktion success () und error () das Antwortobjekt entpackt, sodass die Rückrufsignatur wie $ http (...) lautet. Success (Funktion (Daten, Status, Header, Konfiguration))

Für then () werden Sie sich wahrscheinlich mit dem rohen Antwortobjekt befassen. wie im AngularJS $ http API-Dokument veröffentlicht

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

Der letzte .catch (...) wird nur benötigt, wenn in der vorherigen Versprechenskette ein neuer Fehler aufgetreten ist.

zd333
quelle
2
Erfolgs- / Fehlermethoden sind veraltet.
OverMars
-3

Ich mache es so, wie Bradley Braithwaite es in seinem Blog vorschlägt :

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

Wichtige Punkte:

  • Die Auflösungsfunktion ist mit der .then-Funktion in unserem Controller verknüpft, dh alles ist in Ordnung, sodass wir unser Versprechen halten und es auflösen können.

  • Die Ablehnungsfunktion ist mit der .catch-Funktion in unserem Controller verknüpft, dh es ist ein Fehler aufgetreten, sodass wir unser Versprechen nicht einhalten können und es ablehnen müssen.

Es ist ziemlich stabil und sicher, und wenn Sie andere Bedingungen haben, um das Versprechen abzulehnen, können Sie Ihre Daten jederzeit in der Erfolgsfunktion filtern und deferred.reject(anotherReason)mit dem Grund der Ablehnung anrufen .

Wie Ryan Vice in den Kommentaren angedeutet hat , kann dies nicht als nützlich angesehen werden, es sei denn, Sie spielen sozusagen ein bisschen mit der Antwort herum.

Weil successund errorseit 1.4 veraltet sind, ist es vielleicht besser, die regulären Versprechensmethoden zu verwenden thenund catchdie Antwort innerhalb dieser Methoden zu transformieren und das Versprechen dieser transformierten Antwort zurückzugeben.

Ich zeige das gleiche Beispiel mit beiden Ansätzen und einem dritten Zwischenansatz:

successund errorAnsatz ( successund errorein Versprechen einer HTTP-Antwort zurückgeben, daher benötigen wir die Hilfe $q, um ein Versprechen von Daten zurückzugeben):

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

thenund catchAnnäherung (dies ist aufgrund des Wurfs etwas schwieriger zu testen):

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

Es gibt jedoch eine Lösung auf halbem Weg (auf diese Weise können Sie das vermeiden throwund müssen es wahrscheinlich verwenden $q, um das Versprechen in Ihren Tests zu verspotten):

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

Kommentare oder Korrekturen jeglicher Art sind willkommen.

Uhrmacher
quelle
2
Warum sollten Sie $ q verwenden, um das Versprechen in ein Versprechen zu verpacken? Warum nicht einfach das Versprechen zurückgeben, das von $ http.get () zurückgegeben wird?
Ryan Vice
Weil success()und error()würde kein neues Versprechen zurückgeben, wie es then()tut. Mit $qmachen wir unsere Fabrik, um ein Versprechen von Daten anstelle eines Versprechens einer HTTP-Antwort zurückzugeben.
Uhrmacher
Ihre Antwort ist verwirrend für mich. Vielleicht erkläre ich mich nicht gut. Wenn Sie die Antwort nicht manipulieren, können Sie einfach das Versprechen zurückgeben, dass $ http zurückgegeben wird. siehe dieses Beispiel, das ich gerade geschrieben habe: jsbin.com/belagan/edit?html,js,output
Ryan Vice
1
Ich sehe den Wert nicht. Es fühlt sich für mich unnötig an und ich lehne Codeüberprüfungen für meine Projekte ab, die diesen Ansatz verwenden. Wenn Sie jedoch Wert daraus ziehen, sollten Sie ihn verwenden. Ich habe auch ein paar Versprechen in eckigen Best-Practice-Artikeln gesehen, in denen unnötiges Verpacken als Geruch bezeichnet wird.
Ryan Vice