¿Cómo esperar hasta que la respuesta provenga de la solicitud $ http, en angularjs?

93

Estoy usando algunos datos que provienen de un servicio RESTful en varias páginas. Entonces estoy usando fábricas angulares para eso. Por lo tanto, necesitaba obtener los datos una vez del servidor y cada vez que obtengo los datos con ese servicio definido. Como una variable global. Aquí está la muestra:

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

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

En mi controlador estoy usando este servicio como:

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

Está funcionando bien para mí según mis requisitos. Pero el problema aquí es que cuando vuelvo a cargar en mi página web, el servicio se llamará nuevamente y se solicitará el servidor. Si en el medio se ejecuta alguna otra función que depende del "servicio definido", está dando el error como "algo" no está definido. Entonces quiero esperar en mi script hasta que se cargue el servicio. ¿Cómo puedo hacer eso? ¿Hay de todos modos hacer eso en angularjs?

anilCSE
fuente

Respuestas:

150

Debe usar promesas para operaciones asincrónicas en las que no sepa cuándo se completará. Una promesa "representa una operación que aún no se ha completado, pero que se espera en el futuro". ( https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise )

Una implementación de ejemplo sería como:

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

Editar: Con respecto al comentario de Sujoy, ¿Qué debo hacer para que la llamada a myFuction () no regrese hasta que la función .then () finalice la ejecución?

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

Bueno, supongamos que la llamada a getData () tardó 10 segundos en completarse. Si la función no devuelve nada en ese tiempo, se convertirá efectivamente en un código sincrónico normal y colgará el navegador hasta que se complete.

Sin embargo, con la promesa que regresa instantáneamente, el navegador es libre de continuar con otro código mientras tanto. Una vez que la promesa se resuelve / falla, se activa la llamada then (). Por lo tanto, tiene mucho más sentido de esta manera, incluso si puede hacer que el flujo de su código sea un poco más complejo (¡la complejidad es un problema común de la programación asíncrona / paralela en general, después de todo!)

mikel
fuente
2
¡Esto resolvió mi problema! Para cualquier otra persona, tenía un menú desplegable que necesitaba datos de la llamada ajax, por lo que cuando se creó el alcance, los datos no estaban disponibles. Con este aplazamiento, se puede asignar el alcance para que los datos provengan de la llamada ajax.
Kat Lim Ruiz
8
@mikel: Tengo otra pregunta aquí. Su llamada a myFuction () regresará inmediatamente pero esa promesa .then () llamará más tarde. ¿Qué debo hacer para que la llamada a myFuction () no regrese hasta que la función .then () finalice la ejecución? 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 las personas nuevas en esto, también puede usar una devolución de llamada, por ejemplo:

A tu servicio:

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

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

En tu controlador:

    DataHandler.GetRandomArtists(3, function(response){
      $scope.data.random_artists = response;
   });
Raul gomez
fuente
Gran solucion Estaba pensando en la misma línea cuando lo estaba investigando. Me alegro de que alguien haya publicado esto.
Nate
0

Para su información, esto está usando Angularfire, por lo que puede variar un poco para un servicio diferente u otro uso, pero debería resolver lo mismo que tiene $ http. Tenía este mismo problema, la única solución que se adaptaba mejor a mí era combinar todos los servicios / fábricas en una única promesa en el alcance. En cada ruta / vista que necesitaba que se carguen estos servicios / etc., coloco las funciones que requieren datos cargados dentro de la función del controlador, es decir, myfunct () y la aplicación principal.js en ejecución después de la autenticación que puse

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

y en la vista acabo de hacer

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

en el primer elemento / contenedor de vista principal para que el controlador pueda ejecutar todo lo que hay dentro

myfunct()

sin preocuparse por las promesas asincrónicas / pedidos / problemas de cola. Espero que eso ayude a alguien con los mismos problemas que yo tuve.

Samuel Barney
fuente
0

Estaba teniendo el mismo problema y ninguno si esto funcionó para mí. Sin embargo, esto es lo que funcionó ...

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

    return { data: data }
});

y luego la función que lo usa es ...

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

El servicio no es tan necesario, pero creo que es una buena práctica para la extensibilidad. La mayor parte de lo que necesitará para uno será para cualquier otro, especialmente cuando utilice API. De todos modos espero que esto haya sido útil.

user3127557
fuente