Usando sucesso / erro / finalmente / captura com promessas em AngularJS

112

Estou usando $httpem AngularJs e não tenho certeza de como usar a promessa retornada e como lidar com erros.

Eu tenho 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
    });

Essa é uma boa maneira de fazer isso ou existe uma maneira mais fácil?

Joel
fonte

Respostas:

103

As promessas são uma abstração sobre as declarações que nos permitem nos expressar de forma síncrona com o código assíncrono. Eles representam a execução de uma tarefa única.

Eles também fornecem tratamento de exceção, assim como o código normal, você pode retornar de uma promessa ou pode lançar.

O que você deseja no código síncrono é:

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

A versão prometida é muito semelhante:

$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
fonte
4
Como você usaria success(), error()e finally()combinado com catch()? Ou eu tenho que usarthen(successFunction, errorFunction).catch(exceotionHandling).then(cleanUp);
Joel
3
@ Joel geralmente, você não quer usar sempre successe error(de preferência .thene .catchem vez disso, você pode (e deve) omitir o errorFunctiondo .thenac uso catchcomo no meu código acima).
Benjamin Gruenbaum
@BenjaminGruenbaum você poderia explicar por que sugere evitar success/ error? Além disso, meu Eclipse fica louco quando vê o .catch(, então eu uso ["catch"](por agora. Como posso domar o Eclipse?
Giszmo
A implementação do módulo $ http do Angular da biblioteca $ q usa .success e .error em vez de .then e .catch. No entanto, em meus testes, pude acessar todas as propriedades da promessa $ http ao usar as promessas .then e .catch. Veja também a resposta de zd333.
Steve K
3
@SirBenBenji $ q não tem .successe .error, $ http retorna uma promessa $ q com a adição dos manipuladores successe error- no entanto, esses manipuladores não são encadeados e geralmente devem ser evitados se / quando possível. Em geral - se você tiver dúvidas, é melhor fazer como uma pergunta nova e não como um comentário sobre uma pergunta antiga.
Benjamin Gruenbaum
43

Esqueça sobre o uso successe errormétodo.

Ambos os métodos foram descontinuados no angular 1.4. Basicamente, o motivo da reprovação é que eles não são compatíveis com cadeias , por assim dizer.

Com o exemplo a seguir, vou tentar demonstrar o que quero dizer sobre successe errornão sendo chainable-friendly . Suponha que chamemos uma API que retorna um objeto de usuário com um endereço:

Objeto do usuário:

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

Chamada para a 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'}
    });
};

O que aconteceu?

Porque successe errorretornar a promessa original , ou seja, aquela retornada por $http.get, o objeto passado para o retorno de chamada de thené o objeto de usuário inteiro , ou seja, a mesma entrada para o successretorno de chamada anterior .

Se tivéssemos encadeado dois then, isso teria sido menos confuso:

$http.get('/user')
    .then(function (user) {
        return user.address;  
    })
    .then(function (obj) {  
        console.log(obj); // -> 'San Francisco'
    });
};
Michael P. Bazos
fonte
1
Também vale a pena notar que successe errorsão adicionados apenas ao retorno imediato da $httpchamada (não ao protótipo), então se você chamar outro método de promessa entre eles (como, você normalmente chama return $http.get(url)empacotado em uma biblioteca base, mas depois decide alternar um spinner em a chamada da biblioteca com return $http.get(url).finally(...)), você não terá mais esses métodos de conveniência.
drzaus
35

Acho que as respostas anteriores estão corretas, mas aqui está outro exemplo (apenas um fyi, success () e error () estão obsoletos de acordo com a página principal do 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
fonte
3
Finalmente não retorna a resposta, que eu saiba.
diplossauro,
11

Que tipo de granularidade você está procurando? Normalmente, você pode sobreviver com:

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

Descobri que "finalmente" e "pegar" são melhores quando encadeamos várias promessas.

justin
fonte
1
Em seu exemplo, o manipulador de erros lida apenas com erros $ http.
Benjamin Gruenbaum
1
Sim, ainda preciso lidar com exceções nas funções de sucesso / erro também. E então eu preciso de algum tipo de gerenciador comum (onde posso definir coisas como loading = false)
Joel
1
Você tem uma chave em vez de parênteses fechando sua chamada then ().
Paul McClean
1
Isso não funciona em erros de resposta 404, só funciona no .catch()Método
elporfirio
Esta é a resposta correta para lidar com erros de http retornados aos controladores
Leon,
5

No caso de $ http Angular, as funções success () e error () terão o objeto de resposta desembrulhado, de modo que a assinatura de retorno de chamada seria como $ http (...). Success (function (data, status, headers, config))

para then (), você provavelmente lidará com o objeto de resposta bruto. como postado no documento de API $ http do 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;
    });

O último .catch (...) não será necessário, a menos que haja um novo erro na cadeia de promessa anterior.

zd333
fonte
2
Os métodos de sucesso / erro estão obsoletos.
OverMars
-3

Eu faço isso como Bradley Braithwaite sugere em seu 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;
            });
    }])

Pontos chave:

  • A função de resolução se vincula à função .then em nosso controlador, ou seja, está tudo bem, então podemos manter nossa promessa e resolvê-la.

  • A função de rejeição é vinculada à função .catch em nosso controlador, ou seja, algo deu errado, então não podemos cumprir nossa promessa e precisamos rejeitá-la.

É bastante estável e seguro e se tiver outras condições para rejeitar a promessa pode sempre filtrar os seus dados na função de sucesso e ligar deferred.reject(anotherReason)com o motivo da rejeição.

Como Ryan Vice sugeriu nos comentários , isso pode não ser visto como útil, a menos que você altere um pouco a resposta, por assim dizer.

Porque successe errorsão obsoletas desde 1.4 talvez seja melhor usar os métodos promessa regulares thene catche transformar a resposta dentro desses métodos e retornar a promessa de que a resposta transformado.

Estou mostrando o mesmo exemplo com ambas as abordagens e uma terceira abordagem intermediária:

successe errorabordagem ( successe errorretornar uma promessa de uma resposta HTTP, portanto, precisamos da ajuda de $qpara retornar uma promessa de dados):

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 catchabordagem (isso é um pouco mais difícil de testar, por causa do lançamento):

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

Porém, há uma solução intermediária (desta forma, você pode evitar o throwe, de qualquer forma, provavelmente precisará usar $qpara simular o comportamento de promessa em seus testes):

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

Qualquer tipo de comentários ou correções são bem-vindos.

Relojoeiro
fonte
2
Por que você usaria $ q para envolver a promessa em uma promessa. Por que não apenas retornar a promessa que foi retornada por $ http.get ()?
Ryan Vice
Porque success()e error()não retornaria uma nova promessa como then()faz. Com $q, fazemos nossa fábrica retornar uma promessa de dados em vez de uma promessa de uma resposta HTTP.
Relojoeiro de
sua resposta é confusa para mim, então talvez eu não esteja me explicando bem. a menos que esteja manipulando a resposta, você pode simplesmente retornar a promessa de que $ http retornou. veja este exemplo que acabei de escrever: jsbin.com/belagan/edit?html,js,output
Ryan Vice
1
Não vejo o valor. Parece desnecessário para mim e rejeito revisões de código em meus projetos que usam essa abordagem, mas se você está obtendo valor com isso, você deve usá-la. Também vi algumas promessas em artigos de práticas recomendadas angulares que chamam de embrulho desnecessário como um cheiro.
Ryan Vice
1
Este é um antipadrão adiado . Leia Você está perdendo o
objetivo