8

I have two directives, each consuming the same factory wrapping a $q/$http call.

angular.module("demo").directive("itemA", ["restService", function(restService) {
    return {
        restrict: "A",
        link: function(scope, element, attrs) {
            restService.get().then(function(response) {
                // whatever
            }, function(response) {
               // whatever
            });
        }
    };
}]);


angular.module("demo").directive("itemB", ["restService", function(restService) {
    return {
        restrict: "A",
        link: function(scope, element, attrs) {
            restService.get().then(function(response) {
                // whatever
            }, function(response) {
               // whatever
            });
        }
    };
}]);

angular.module("demo").factory("restService", ["$http", "$q", function($http, $q) {
    return {
       get: function() {
           var dfd = $q.defer();
           $http.get("whatever.json", {
               cache: true
           }).success(function(response) {
              // do some stuff here
              dfd.resolve(response);
           }).error(function(response) {
              // do some stuff here
              dfd.reject(response);
           });
       }
    };
}]);

Problem: When I do this

<div item-a></div>
<div item-b></div>

I get the same web service fired off twice, because the GET from ItemA is still in progress when the GET for ItemB goes.

Is there a way for whichever fires second to know that there's already a request to this in progress, so that it can wait a minute and grab it for free?

I've thought about making an $http or $q wrapper which flags each URL as pending or not but I'm not sure that's the best way. What would I do if it was pending? Just return the existing promise and it'll resolve when the other resolves?

oooyaya
  • 1,773
  • 1
  • 15
  • 39

1 Answers1

18

Yes, all you need to do is to cache the promise and clean it off after the request is done. Any subsequent request in between can just use the same promise.

angular.module("demo").factory("restService", ["$http", "$q", function($http, $q) {
    var _cache;
    return {
       get: function() {
          //If a call is already going on just return the same promise, else make the call and set the promise to _cache
          return _cache || _cache = $http.get("whatever.json", {
               cache: true
           }).then(function(response) {
              // do some stuff here
              return response.data;
           }).catch(function(response) {
              return $q.reject(response.data);
           }).finally(function(){
              _cache = null; //Just remove it here
           });
      }
   };
}]);
PSL
  • 123,204
  • 21
  • 253
  • 243
  • This was very helpful. After a long day I guess I just couldn't think to recycle the promise I was already making. I modified it a little because we're in strict mode, but it's 90% what you had. – oooyaya Jan 15 '15 at 22:25
  • @oooyaya cool.:) feel free to update my answer if you think it is relevant. – PSL Jan 15 '15 at 22:33
  • nah, what you have is alright. there was one weird thing. we use the deferred pattern so the .then in the provider eventually invokes a .then in the directive via resolve. however, using .finally(), the order would be provider's then, provider's finally, directive's then - so, by the time the directive's then() went, _dfd was cleared and it blew up. so, we just unset _dfd in the provider, but after we resolve. i imagine there's a better way but this is just for a POC. – oooyaya Jan 16 '15 at 20:00
  • You do not need to use deferred pattern at all in your case. Read [this](http://stackoverflow.com/questions/23803743/what-is-the-deferred-antipattern-and-how-do-i-avoid-it) And remember the promise chains will be executed in the exact order as they are regsitered. – PSL Jan 16 '15 at 20:41
  • 4
    I really like this answer too (especially with reading material in the comments), but my linter was crapping out at return _cache || _cache = fn(). Apparently, it's bad practice (maintainably ambiguous) to use assignment in a return statement. Just a nit-pick, but linter no likey (http://eslint.org/docs/rules/no-return-assign.html). Easily fixed with return _cache || (_cache = fn()). – coblr Jun 29 '15 at 18:28
  • I fixed the assignment errors like this: return _response ? _response : _response = $http({... otherwise nice example – v0d1ch Jan 19 '17 at 11:59