Kolby Kolby - 1 month ago 4
Javascript Question

Preventing multiple $http requests in Angular. Is there a better way?

I've built a somewhat complex method for returning a resources via $http.

The method returns a promise and then checks my local cache if the resources exists yet. If it does it will return the cached resources, if not it will make the $http request. This works great after the resource has been cached, but I have multiple functions through out the application that is hitting this method on load, and every one of them will make the http request because the resources hasn't been returned and cached yet.

I came up with a simple check that fixes this, but I feel like there should be a better way. I added a boolean that is set to true if the method is in the middle of getting the resource, and if it is I resolve the method with a half second timeout, to give the request time to resolve. Code is below.

So, is there a better way?

var schools = [];
var loadingSchools = false;

function getAllSchools(forceUpdate) {
return $q(function (resolve, reject) {
if(loadingSchools) resolve($timeout(getAllSchools, 500));

else{

loadingSchools = true;

if (schools.length && !forceUpdate) {
loadingSchools = false;
resolve(schools);
return;
}

console.log('$http: Getting All Schools - schoolService.js');

$http.get(API_PATH + 'schools_GetAll', {cache:true})
.success(function(result) {
schools = result;
loadingSchools = false;
resolve(schools);
})
.error(function(error) {
schools = [];
loadingSchools = false;
reject(error);
});
}
});
}

Answer

First off, I don't think it's necessary to wrap the HttpPromise into another promise. With the success/error methods deprecated, you should rely solely on the then() method, and treat the HttpPromise as just a regular promise.

In order to make sure that the request is sent out only once, you can actually keep track of the first HttpPromise you create, and on subsequent calls of the function, return that same promise.

Here is a service which will accept an API endpoint as an argument, and ensure that only one request is sent out to that API.

app.factory('$httpOnce', [ '$http', '$cacheFactory',
  function ($http, $cacheFactory) {
    var cache = $cacheFactory('$httpOnce');

    return function $httpOnce(url, options) {
      return cache.get(url) || cache.put(url, $http.get(url, options)
        .then(function (response) {
          return response.data;
        }));
    };
  }
]);

Usage

function log(data) {
  console.log(data);
}

// issues an HTTP request
$httpOnce('https://api.github.com/').then(log);
// does not issue an HTTP request, returns the same promise as above
$httpOnce('https://api.github.com/').then(log);

// ...
// HTTP request completes somewhere, both promises above are resolved
// ...

setTimeout(function () {
  // immediately resolved
  $httpOnce('https://api.github.com/').then(log);
}, 5000);

Here is a demo. You can see in the dev tools that only one request is issued.

Comments