Como esperar até que a resposta venha da solicitação $ http, no angularjs?

93

Estou usando alguns dados de um serviço RESTful em várias páginas. Estou usando fábricas angulares para isso. Então, eu preciso obter os dados uma vez do servidor, e toda vez que estou obtendo os dados com aquele serviço definido. Exatamente como variáveis ​​globais. Aqui está o exemplo:

var myApp =  angular.module('myservices', []);

myApp.factory('myService', function($http) {
    $http({method:"GET", url:"/my/url"}).success(function(result){
        return result;
    });
});

No meu controlador, estou usando este serviço como:

function myFunction($scope, myService) {
    $scope.data = myService;
    console.log("data.name"+$scope.data.name);
}

Está funcionando bem para mim de acordo com minhas necessidades. Mas o problema aqui é que quando eu recarreguei na minha página o serviço será chamado novamente e requisições de servidor. Se no meio alguma outra função é executada que é dependente do "serviço definido", está dando o erro como "algo" é indefinido. Portanto, quero esperar em meu script até que o serviço seja carregado. Como eu posso fazer isso? Existe alguma maneira de fazer isso no angularjs?

anilCSE
fonte

Respostas:

150

Você deve usar promessas para operações assíncronas em que não sabe quando elas serão concluídas. Uma promessa "representa uma operação que ainda não foi concluída, mas é esperada no futuro." ( https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise )

Um exemplo de implementação seria:

myApp.factory('myService', function($http) {

    var getData = function() {

        // Angular $http() and then() both return promises themselves 
        return $http({method:"GET", url:"/my/url"}).then(function(result){

            // What we return here is the data that will be accessible 
            // to us after the promise resolves
            return result.data;
        });
    };


    return { getData: getData };
});


function myFunction($scope, myService) {
    var myDataPromise = myService.getData();
    myDataPromise.then(function(result) {  

       // this is only run after getData() resolves
       $scope.data = result;
       console.log("data.name"+$scope.data.name);
    });
}

Edit: Em relação ao comentário Sujoys que o que eu preciso fazer para que a chamada myFuction () não retorne até que a função .then () termine a execução.

function myFunction($scope, myService) { 
    var myDataPromise = myService.getData(); 
    myDataPromise.then(function(result) { 
         $scope.data = result; 
         console.log("data.name"+$scope.data.name); 
    }); 
    console.log("This will get printed before data.name inside then. And I don't want that."); 
 }

Bem, vamos supor que a chamada para getData () levou 10 segundos para ser concluída. Se a função não retornasse nada nesse tempo, ela se tornaria efetivamente um código síncrono normal e travaria o navegador até que fosse concluído.

Com a promessa retornando instantaneamente, o navegador está livre para continuar com outro código enquanto isso. Assim que a promessa é resolvida / falha, a chamada then () é disparada. Portanto, faz muito mais sentido dessa forma, mesmo que torne o fluxo do seu código um pouco mais complexo (afinal, a complexidade é um problema comum de programação assíncrona / paralela em geral!)

mikel
fonte
2
Isso resolveu meu problema !!! Para qualquer outra pessoa, eu tinha uma lista suspensa que precisava de dados de uma chamada ajax, portanto, quando o escopo foi criado, os dados não estavam disponíveis. Com esse adiamento, o escopo pode ser atribuído para ter os dados provenientes da chamada ajax.
Kat Lim Ruiz
8
@mikel: Tenho outra pergunta aqui. Sua chamada myFuction () retornará imediatamente, mas essa promessa .então () será chamada mais tarde. O que preciso fazer para que a chamada myFuction () não retorne até que a função .then () termine a execução. function myFunction($scope, myService) { var myDataPromise = myService.getData(); myDataPromise.then(function(result) { $scope.data = result; console.log("data.name"+$scope.data.name); }); console.log("This will get printed before data.name inside then. And I don't want that."); }
Sujoy
13

para quem não conhece, você também pode usar um retorno de chamada, por exemplo:

Em seu serviço:

.factory('DataHandler',function ($http){

   var GetRandomArtists = function(data, callback){
     $http.post(URL, data).success(function (response) {
         callback(response);
      });
   } 
})

Em seu controlador:

    DataHandler.GetRandomArtists(3, function(response){
      $scope.data.random_artists = response;
   });
Raul Gomez
fonte
Ótima solução. Estava pensando nessa mesma linha quando estava investigando isso. Que bom que alguém divulgou isso.
Nate
0

Para sua informação, este é o uso do Angularfire, então pode variar um pouco para um serviço diferente ou outro uso, mas deve resolver o mesmo problema que $ http tem. Eu tinha esse mesmo problema, apenas a solução que me cabia o melhor era combinar todos os serviços / fábricas em uma única promessa no escopo. Em cada rota / visualização que precisava que esses serviços / etc fossem carregados, coloquei quaisquer funções que requeiram dados carregados dentro da função do controlador, isto é, myfunct () e o app.js principal em execução após a autenticação

myservice.$loaded().then(function() {$rootScope.myservice = myservice;});

e na vista acabei de fazer

ng-if="myservice" ng-init="somevar=myfunct()"

no primeiro elemento / wrapper / visão pai para que o controlador possa executar tudo dentro

myfunct()

sem se preocupar com promessas assíncronas / pedidos / problemas de fila. Espero que ajude alguém com os mesmos problemas que eu tive.

Samuel Barney
fonte
0

Eu estava tendo o mesmo problema e nenhum se isso funcionasse para mim. Aqui está o que funcionou ...

app.factory('myService', function($http) {
    var data = function (value) {
            return $http.get(value);
    }

    return { data: data }
});

e então a função que o usa é ...

vm.search = function(value) {

        var recieved_data = myService.data(value);

        recieved_data.then(
            function(fulfillment){
                vm.tags = fulfillment.data;
            }, function(){
                console.log("Server did not send tag data.");
        });
    };

O serviço não é tão necessário, mas acho que é uma boa prática para extensibilidade. Muito do que você precisa para um será para o outro, especialmente ao usar APIs. De qualquer forma, espero que isso tenha sido útil.

user3127557
fonte