Служба AngularJS, хранящая результаты $http для предотвращения повторного запроса, есть ли лучший способ сделать это?

Настройка: я хочу иметь службу, которую несколько контроллеров могут запрашивать для получения данных, используя $http. Первоначальное решение заключалось в использовании промисов, как было предложено здесь.

Проблема: каждый раз, когда контроллер запрашивает службу, служба затем возвращает обещание $http, что приводит к нескольким запросам, которые снова и снова извлекают одни и те же данные с удаленного сервера.

Решение. Сервисная функция возвращает либо данные, либо обещание, как показано ниже. И это зависит от контроллера, чтобы проверить и действовать соответственно.

app.factory('myService', function($http) {
    var items = [];
    var myService = {
        getItems: function() {
            // if items has content, return items; otherwise, return promise.
            if (items.length > 0) {
                return items;
            } else {     
                var promise = $http.get('test.json').then(function (response) {
                    // fill up items with result, so next query just returns items.
                    for(var i=0;i<response.data.length;i++){
                        items.push(response.data[i]);
                    }
                    return items;
                });
                // Return the promise to the controller
                return promise;
            }
     };
     return myService;
});

Поэтому, когда контроллеру нужны эти данные, контроллер просто делает что-то вроде этого:

app.controller('MainCtrl', function( myService,$scope) {
    var promiseOrData = myService.async();
    // Check whether result is a promise or data.
    if ( typeof promiseOrData.then === 'function'){
        // It's a promise.  Use then().
        promiseOrData.then( function(data ){
            $scope.data = data;
        });
    } else {
        // It's data.
        $scope.data = data;
    }
});

Вопрос в следующем: есть ли лучший способ сделать это? Со многими контроллерами этот метод будет иметь много повторяющегося кода. В идеале контроллеры будут просто напрямую запрашивать данные у службы.

Спасибо!


person RVC    schedule 09.08.2013    source источник


Ответы (2)


$http возвращает обещание, мы можем использовать его вместо создания нового с помощью $q. Как только обещание выполнено, мы можем продолжать возвращать его.

.factory('myService', ['$http','$q', function($http, $q) {
    var items = [];
    var last_request_failed = true;
    var promise = undefined;
    return {
        getItems: function() {
            if(!promise || last_request_failed) {
                promise = $http.get('test.json').then(
                function(response) {
                    last_request_failed = false;
                    items = response.data;
                    return items;
                },function(response) {  // error
                    last_request_failed = true;
                    return $q.reject(response);
                });
            }
            return promise;
        },
    };
}])

В вашем контроллере:

myService.getItems().then( 
    function(data) { $scope.data = data; }
);
person Mark Rajcok    schedule 09.08.2013
comment
Хорошо сделано. Еще лучше это уменьшает количество кода, необходимого в контроллере. - person Jonathan Palumbo; 09.08.2013
comment
Любой способ сделать это более СУХИМ на стороне услуг? У меня есть несколько конечных точек http, для которых я хочу это сделать, и повторение этого кода для каждой конечной точки службы/http выйдет из-под контроля. - person Kyle Kochis; 26.08.2013
comment
@KyleKochis, единственное, что мне удалось сделать, это создать один HTTP-перехватчик для перехвата ошибок HTTP для всех my services, поэтому приведенный выше код больше не нуждается в обработчике ошибок. - person Mark Rajcok; 26.08.2013
comment
@MarkRajcok var last_request_failed = false будет лучше? Если он объявлен как true, то будет ли вызов .getItems() до того, как $http.get().then() запустится и передаст ответ одной из своих функций, снова вызовет $http.get()? - person paulhhowells; 10.10.2013
comment
@paulhhowells, если у вас включено кеширование, если есть несколько запросов GET для одного и того же URL-адреса, которые должны быть кэшированы с использованием одного и того же кеша, но кеш еще не заполнен, будет сделан только один запрос к серверу, а остальные запросы будут выполняться с использованием ответа на первый запрос. -- ref Так что я думаю, что это нормально, но я не уверен. - person Mark Rajcok; 10.10.2013
comment
Я знаю, что эта ветка действительно старая, но ответ выше был ближе всего к решению моей проблемы... единственная проблема в том, что я хочу, чтобы метод .getItems() проверял, был ли запрос $http выполнен за последний час , если да, то использовать кешированные данные, а если нет, то снова запустить запрос. Может кто-нибудь помочь с этим? - person Paulos3000; 24.05.2016
comment
Контроллер должен быть myService.getItems().then( function(data ){ $scope.data = data; }); вместо $scope.data = myService.getItems(); правильно? - person T J; 14.07.2016
comment
@TJ, да, я обновлю. Первоначально шаблоны AngularJS могли обрабатывать промис, но в какой-то момент (не помню когда) они устарели от этой функциональности. - person Mark Rajcok; 14.07.2016

Создайте свое собственное обещание, которое разрешается либо к кэшированным данным, либо к извлеченным данным.

app.factory('myService', function($http, $q) {
    var items = [];
    var myService = {
        getItems: function() {
            var deferred =  $q.defer();

            if (items.length > 0) {
                 //resolve the promise with the cached items
                deferred.resolve(items);
            } else {     
                $http.get('test.json').then(function (response) {
                    // fill up items with result, so next query just returns items.
                    for(var i=0;i<response.data.length;i++){
                        items.push(response.data[i]);
                    }
                    //resolve the promise with the items retrieved
                    deferred.resolve(items);
                },function(response){
                   //something went wrong reject the promise with a message
                   deferred.reject("Could not retrieve data!"); 
                });


            }
         // Return the promise to the controller
         return deferred.promise;
     };
     return myService;
});

Затем используйте обещание в своем контроллере.

app.controller('MainCtrl', function( myService,$scope) {
    var promiseOrData = myService.getItems();

        promiseOrData.then( function(data){
            $scope.data = data;
        },
        function(data){
            // should log "Could not retrieve data!"
            console.log(data)
        });

});
person Jonathan Palumbo    schedule 09.08.2013