Calling only once / caching the data from a $http get in an AngularJS service

末鹿安然 提交于 2019-12-04 13:26:31

问题


This may sound like a really simply/stupid question but I need to ask it as I haven't came across this scenario before... okay I have a service in my angularJS app. this service currently contains 4 methods that all perform 80% the same functionality/code and I wish to make this more efficient. Here is what my service looks like (with a lot of code removed):

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

        var townList = {};

        townList.getTownList = function () {

            return $http({method: 'GET', url: '/api/country/cities'})
                .then(function (response) {

                    // HERE WE FORMAT THE response as desired... that creates a returnArray
                    var returnArray = [];
                    // loop through the countries
                    var JsonData = response.data;

                    for (key in JsonData['countries']) {
                         // formatting code...
                    }
                    // end of repeated CODE

                    return returnArray; // this is array, we don't do any formatting here

                });
        };


     townList.getCurrentTown = function (place) {

            return $http({method: 'GET', url: '/api/country/cities'})
                .then(function (response) {

                    // HERE WE FORMAT THE response as desired... that creates a returnArray
                    var returnArray = [];
                    // loop through the countries
                    var JsonData = response.data;

                    for (key in JsonData['countries']) {
                         // formatting code...
                    }
                    // end of repeated code

                    // now the format further / work with the returnArray...
                    for (var i = 0; i < returnArray.length; i++) {
                        // do stuff
                    }
                    return currentTown; // this is a string

                });
        };


        townList.getCurrentCountry = function (place) {

            return $http({method: 'GET', url: '/api/country/cities'})
                .then(function (response) {

                    // HERE WE FORMAT THE response as desired... that creates a returnArray
                    var returnArray = [];
                    // loop through the countries
                    var JsonData = response.data;

                    for (key in JsonData['countries']) {
                         // formatting code...
                    }
                    // end of repeated code

                    // now the format further / work with the returnArray...
                    for (var i = 0; i < returnArray.length; i++) {
                        // do stuff
                    }
                    return currentCountry; // this is a string

                });
        };

        return townList;

    }
)
;

Now I repeat the same $http 'GET' in each method and the same formatting code (which is a lot of nested loops) before returning a object array or a string. This is far from efficent! What is the best way to put this functionality into it's own function so we only call the GET url once but still return a promise with each method? Should I set the results of the $http({method: 'GET', url: '/api/country/cities'}) as a var and inject / pass it into each method before formatting the data if necessary? Should I use some sort of $cacheFactory?

Sorry if this is a dumb question and if I haven't explained myself well I shall rephrase the questions.

Thanks in advance.


回答1:


It is just as you say; this code can (and should) be refactored in many ways. One example:

Let us factor the HTTP stuff into a separate service, that will also take care of caching. (Another idea for this would be to have a service for the HTTP/remote calls and another - maybe a general use decorator - to handle caching. LEt us not go into so much detail for now.) And let us put the formatting code in another method:

The remote call service:

.service('townHttpService', function($http, $q) {
    var cache;

    function getCities() {
        var d = $q.defer();
        if( cache ) {
            d.resolve(cache);
        }
        else {
            $http({method: 'GET', url: '/api/country/cities'}).then(
                function success(response) {
                    cache = response.data;
                    d.resolve(cache);
                },
                function failure(reason) {
                    d.reject(reason);
                }
            });
        }
        return d.promise;
    }

    function clearCache() {
        cache = null;
    }

    return {
        getCities: getCities,
        clearCache: clearCache
    };
})

The formatter:

.service('townFormatter', function() {
    return function townFormatter(jsonData) {
        // HERE WE FORMAT THE response as desired... that creates a returnArray
        var returnArray = [], key;
        // loop through the countries
        for (key in jsonData['countries']) {
             // formatting code...
        }
        // end of repeated CODE
        return returnArray; // this is array, we don't do any formatting here
    };
})

Your townDataService, written in terms of the above:

.factory('townDataService', function (townHttpService, townFormatter) {

    var townList = {};

    townList.getTownList = function () {
        return townHttpService.getCities().then(townFormatter);
    };

    townList.getCurrentTown = function (place) {
        return townHttpService.getCities().then(townFormatter).then(function(cityList) {
            var currentTown;
            for (var i = 0; i < cityList.length; i++) {
                // do stuff
            }
            return currentTown; // this is a string
        });
    };

    townList.getCurrentCountry = function (place) {
        return townHttpService.getCities().then(townFormatter).then(function(cityList) {
            var currentCountry;
            for (var i = 0; i < cityList.length; i++) {
                // do stuff
            }
            return currentCountry; // this is a string
        });

    return townList;
})



回答2:


I guess you got two questions there removing repeated logic and best way to cache results.

First - Removing duplicate code: Looks like townList.getTownList is the common method, the other two methods is an extension of this method. So,

townList.getCurrentTown = function(place) {
  var towns = townList.getTownList();
  for (var i = 0; i < returnArray.length; i++) { //additional stuff
  }
  return currentTown;
};


townList.getCurrentCountry = function(place) {
  var towns = townList.getTownList();
  for (var i = 0; i < returnArray.length; i++) { //additional stuff
  }
  return currentCountry;
};

Second - caching values Now the call is being http made only in townList.getTownList, the logic to cache can be easily implemented here. But this depends on whether the data will be onetime fetch or can be refreshed. One time fetch: just enable the cache in the http call $http({method: 'GET', url: '/api/country/cities', cache:true});

Refresh based on request: I would pass an refresh variable to inform whether data has to be refreshed or not. So if the refresh is true or the townList is empty the data will be fetched.

var srvc = this;
var townList;
townList.getTownList = function(refresh ) {
  if (refresh || !townList) {
    srvc.townList = $http({
      method: 'GET',
      url: '/api/country/cities'
    })
      .then(function(response) {
        var returnArray = [];
        var JsonData = response.data;
        for (var key in JsonData.countries) {}
        return returnArray; // this is array, we don't do any formatting here
      });
  }

  return townList;
};



回答3:


There is nothing special that you can do and receive some considerable benefit. You would definitely need to cache your GET response and refactor a bit to avoid code duplication and improve readability:

.factory('townDataService', function ($http) {
    var getCitiesAsync = function(){
        return $http({method: 'GET', url: '/api/country/cities', cache:true});
    };

    var townList = {};

    townList.getTownList = function () {
        return getCitiesAsync().then(prepareTownList);
    };

    var prepareTownList = function(response){
        //extract towns and do whatever you need
        return result;
    };

    ...

As for using $cacheFactory - seems like an overhead for such a simple scenario, just use built-in cache option.




回答4:


To avoid timing issues, it is perhaps good to extend the solution a little bit:

function getCities() {
        var d = $q.defer();
        if( cache ) {
            d.resolve(cache);
        }
        else {
            $http({method: 'GET', url: '/api/country/cities'}).then(
                function success(response) {
                    if (!cache) {
                        cache = response.data;
                    }
                    d.resolve(cache);
                },
                function failure(reason) {
                    d.reject(reason);
                }
            });
        }
        return d.promise;
    }

After a (perhaps second or third) call to the webservice succeeds, one checks if the cache variable was set while waiting for server response. If so, we can return the already assigned value. That way, there will be no new assignment to the variable cache if multiple calls were issued:

function success(response) {
    if (!cache) {
        cache = response.data;

It does not have to make problems but if you rely on having identical objects (for example when working with data binding) it is great to be sure to only fetch data once!



来源:https://stackoverflow.com/questions/24054024/calling-only-once-caching-the-data-from-a-http-get-in-an-angularjs-service

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!