Utilizzo di successo / errore / infine / cattura con Promises in AngularJS


112

Sto usando $httpin AngularJs e non sono sicuro di come utilizzare la promessa restituita e gestire gli errori.

Ho questo codice:

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

È un buon modo per farlo o c'è un modo più semplice?

Risposte:


103

Le promesse sono un'astrazione rispetto alle affermazioni che ci consentono di esprimerci in modo sincrono con il codice asincrono. Rappresentano l'esecuzione di un'attività una tantum.

Forniscono anche la gestione delle eccezioni, proprio come il codice normale, che puoi restituire da una promessa o puoi lanciare.

Quello che vorresti nel codice sincrono è:

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 versione promessa è molto simile:

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

4
Come si usa success(), error()e finally()in combinazione con catch()? Oppure devo usarethen(successFunction, errorFunction).catch(exceotionHandling).then(cleanUp);
Joel

3
@ Joel in generale, non si vuole utilizzare mai successe error(preferire .thene .catchinvece, è possibile omettere (e dovrebbe) il errorFunctiondal .thenac uso catchcome nel mio codice di cui sopra).
Benjamin Gruenbaum

@BenjaminGruenbaum potresti spiegare perché suggerisci di evitare success/ error? Anche il mio Eclipse si impazzisce quando vede il .catch(, quindi ["catch"](per ora lo uso . Come posso domare Eclipse?
Giszmo

L'implementazione del modulo $ http di Angular della libreria $ q utilizza .success e .error invece di .then e .catch. Tuttavia nei miei test ho potuto accedere a tutte le proprietà della promessa $ http quando si utilizzava. Vedi anche la risposta di zd333.
Steve K

3
@SirBenBenji $ q non ha .successe .error, $ http restituisce una $ q promessa con l'aggiunta dei gestori successe error- tuttavia, questi gestori non si concatenano e dovrebbero essere generalmente evitati se / quando possibile. In generale, se hai domande è meglio porle come nuova domanda e non come commento su una vecchia.
Benjamin Gruenbaum

43

Dimentica l'uso successe il errormetodo.

Entrambi i metodi sono stati deprecati in angular 1.4. Fondamentalmente, il motivo dietro la deprecazione è che non sono concatenabili , per così dire.

Con il seguente esempio, cercherò di dimostrare cosa intendo successe errornon essendo concatenabile . Supponiamo di chiamare un'API che restituisce un oggetto utente con un indirizzo:

Oggetto utente:

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

Chiama all'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'}
    });
};

Quello che è successo?

Poiché successe errorrestituisce la promessa originale , ovvero quella restituita da $http.get, l'oggetto passato al callback del thenè l'intero oggetto utente , vale a dire lo stesso input al successcallback precedente .

Se ne avessimo incatenati due then, questo sarebbe stato meno confuso:

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

1
Vale anche la pena notare che successe errorvengono aggiunti solo al ritorno immediato della $httpchiamata (non al prototipo), quindi se chiami un altro metodo di promessa tra di loro (come, normalmente chiami return $http.get(url)avvolto in una libreria di base, ma in seguito decidi di attivare uno spinner in la chiamata della libreria con return $http.get(url).finally(...)), non avrai più quei metodi di convenienza.
drzaus

35

Penso che le risposte precedenti siano corrette, ma ecco un altro esempio (solo un fyi, success () e error () sono deprecati secondo la pagina principale di 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');
    });

3
Infine non restituisce la risposta, per quanto ne so.
diplosaurus

11

Che tipo di granularità stai cercando? In genere puoi cavartela con:

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

Ho scoperto che "finalmente" e "cattura" stanno meglio quando si concatenano più promesse.


1
Nel tuo esempio, il gestore degli errori gestisce solo gli errori $ http.
Benjamin Gruenbaum

1
Sì, devo ancora gestire le eccezioni anche nelle funzioni di successo / errore. E poi ho bisogno di una sorta di gestore comune (dove posso impostare cose come loading = false)
Joel

1
Hai una parentesi graffa invece di una parentesi che chiude la tua chiamata then ().
Paul McClean

1
Questo non funziona con gli errori di risposta 404, funziona solo con il .catch()metodo
elporfirio

Questa è la risposta corretta per la gestione degli errori http restituiti ai controller
Leon

5

Nel caso Angular $ http, le funzioni success () ed error () avranno l'oggetto risposta non incluso nel wrapping, quindi la firma di callback sarà come $ http (...). Success (function (data, status, headers, config))

per then (), probabilmente ti occuperai dell'oggetto di risposta non elaborato. come pubblicato nel documento API http di AngularJS $

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

L'ultimo .catch (...) non sarà necessario a meno che non ci sia un nuovo errore generato nella precedente catena di promesse.


2
I metodi di successo / errore sono deprecati.
OverMars

-3

Lo faccio come suggerisce Bradley Braithwaite nel suo 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;
            });
    }])

Punti chiave:

  • La funzione di risoluzione si collega alla funzione .then nel nostro controller, cioè va tutto bene, quindi possiamo mantenere la nostra promessa e risolverla.

  • La funzione di rifiuto si collega alla funzione .catch nel nostro controller, cioè qualcosa è andato storto, quindi non possiamo mantenere la nostra promessa e dobbiamo rifiutarla.

È abbastanza stabile e sicuro e se hai altre condizioni per rifiutare la promessa puoi sempre filtrare i tuoi dati nella funzione di successo e chiamare deferred.reject(anotherReason)con il motivo del rifiuto.

Come ha suggerito Ryan Vice nei commenti , questo potrebbe non essere visto come utile a meno che non giocherelli un po 'con la risposta, per così dire.

Perché successe errorsono deprecati dal 1,4 forse è meglio usare i metodi promessa regolari thened catche trasformare la risposta all'interno di tali metodi e restituire la promessa di tale risposta trasformata.

Sto mostrando lo stesso esempio con entrambi gli approcci e un terzo approccio intermedio:

successe errorapproccio ( successe errorrestituire una promessa di una risposta HTTP, quindi abbiamo bisogno dell'aiuto di $qper restituire una promessa di dati):

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

thene catchapproccio (questo è un po 'più difficile da testare, a causa del lancio):

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

C'è una soluzione a metà però (in questo modo puoi evitare il throwe comunque probabilmente dovrai usarlo $qper deridere il comportamento della promessa nei tuoi test):

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

Qualsiasi tipo di commento o correzione è il benvenuto.


2
Perché dovresti usare $ q per racchiudere la promessa in una promessa. Perché non restituire semplicemente la promessa restituita da $ http.get ()?
Ryan Vice

Perché success()e error()non restituirebbe una nuova promessa come then()fa. Con $qfacciamo in modo che la nostra fabbrica restituisca una promessa di dati anziché una promessa di una risposta HTTP.
Orologiaio

la tua risposta mi confonde quindi forse non mi sto spiegando bene. a meno che tu non stia manipolando la risposta, puoi semplicemente restituire la promessa restituita da $ http. guarda questo esempio che ho appena scritto: jsbin.com/belagan/edit?html,js,output
Ryan Vice,

1
Non vedo il valore. Non mi sembra necessario e rifiuto le revisioni del codice sui miei progetti che utilizzano questo approccio, ma se ne trai valore, dovresti usarlo. Ho anche visto alcune promesse in articoli di buone pratiche angolari che chiamano un involucro non necessario come odore.
Ryan Vice

1
Questo è un anti-pattern differito . Leggi Stai
perdendo
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.