Jak czekać, aż odpowiedź nadejdzie z żądania $ http w angularjs?

93

Używam niektórych danych, które pochodzą z usługi RESTful na wielu stronach. Więc używam do tego fabryk kątowych. Tak więc musiałem raz pobrać dane z serwera i za każdym razem, gdy otrzymuję dane za pomocą tej zdefiniowanej usługi. Podobnie jak zmienne globalne. Oto próbka:

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

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

W moim kontrolerze korzystam z tej usługi jako:

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

Działa dobrze dla mnie, jak na moje wymagania. Ale problem polega na tym, że po ponownym załadowaniu mojej strony internetowej usługa zostanie ponownie wywołana i poprosi o serwer. Jeśli pomiędzy wykonaniem jakiejś innej funkcji, która jest zależna od „zdefiniowanej usługi”, pojawia się błąd, na przykład „coś” jest niezdefiniowane. Dlatego chcę poczekać w moim skrypcie, aż usługa zostanie załadowana. Jak mogę to zrobić? Czy w ogóle można to zrobić w angularjs?

anilCSE
źródło

Odpowiedzi:

150

Należy używać obietnic dla operacji asynchronicznych, w przypadku których nie wiadomo, kiedy zostanie zakończona. Obietnica „oznacza operację, która jeszcze się nie zakończyła, ale jest oczekiwana w przyszłości”. ( https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise )

Przykładowa implementacja wyglądałaby tak:

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

Edycja: Jeśli chodzi o komentarz Sujoys, co muszę zrobić, aby wywołanie myFuction () nie zwróciło, dopóki funkcja .then () nie zakończy wykonywania.

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

Cóż, załóżmy, że wywołanie getData () zajęło 10 sekund. Jeśli funkcja nie zwróciłaby niczego w tym czasie, faktycznie stałaby się normalnym kodem synchronicznym i zawiesiłaby przeglądarkę do czasu jej zakończenia.

Ponieważ obietnica wraca natychmiast, przeglądarka może w międzyczasie kontynuować pracę z innym kodem. Gdy obietnica zostanie rozwiązana / nie powiedzie się, wywoływane jest wywołanie then (). W ten sposób ma to o wiele więcej sensu, nawet jeśli może sprawić, że przepływ twojego kodu będzie nieco bardziej złożony (złożoność jest w końcu częstym problemem programowania asynchronicznego / równoległego w ogóle!)

mikel
źródło
2
To rozwiązało mój problem !!! Dla każdego innego miałem menu rozwijane, które wymagało danych z wywołania ajax, więc kiedy zakres został utworzony, dane nie były dostępne. Dzięki temu odroczeniu zakres można przypisać do danych pochodzących z wywołania Ajax.
Kat Lim Ruiz
8
@mikel: Mam tutaj inne pytanie. Twoje wywołanie myFuction () zwróci natychmiast, ale ta obietnica .then () zadzwoni później. Co muszę zrobić, aby wywołanie myFuction () nie zwróciło, dopóki funkcja .then () nie zakończy wykonywania. 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

dla nowych osób możesz również skorzystać z połączenia zwrotnego, na przykład:

W Twojej służbie:

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

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

W kontrolerze:

    DataHandler.GetRandomArtists(3, function(response){
      $scope.data.random_artists = response;
   });
Raul Gomez
źródło
Świetne rozwiązanie. Myślałem w tym samym kierunku, kiedy się temu przyglądałem. Cieszę się, że ktoś to tam umieścił.
Nate
0

FYI, to używa Angularfire, więc może się nieco różnić dla innej usługi lub innego zastosowania, ale powinno rozwiązać ten sam problem, który ma $ http. Miałem ten sam problem, jedynym rozwiązaniem, które pasowało mi najlepiej, było połączenie wszystkich usług / fabryk w jedną obietnicę dotyczącą zakresu. Na każdej trasie / widoku, który wymagał załadowania tych usług / itp., Umieszczam wszelkie funkcje, które wymagają załadowanych danych w funkcji kontrolera, tj. Myfunct () i główna aplikacja.js podczas uruchamiania po uwierzytelnieniu

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

iw widoku, który właśnie zrobiłem

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

w pierwszym / nadrzędnym elemencie / opakowaniu widoku, aby kontroler mógł uruchomić wszystko wewnątrz

myfunct()

bez martwienia się o asynchroniczne obietnice / zamówienia / problemy z kolejką. Mam nadzieję, że pomoże to komuś z tymi samymi problemami, co ja.

Samuel Barney
źródło
0

Miałem ten sam problem i żaden, jeśli te zadziałały. Oto, co zadziałało ...

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

    return { data: data }
});

a funkcja, która go używa, to ...

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

Usługa nie jest konieczna, ale myślę, że jest to dobra praktyka w zakresie rozszerzalności. Większość tego, czego będziesz potrzebować do jednego, będzie potrzebna do innego, zwłaszcza podczas korzystania z interfejsów API. W każdym razie mam nadzieję, że to było pomocne.

user3127557
źródło