Skip to content

Instantly share code, notes, and snippets.

@adamreisnz
Last active September 17, 2018 12:10
Show Gist options
  • Star 16 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save adamreisnz/354364e2a58786e2be71 to your computer and use it in GitHub Desktop.
Save adamreisnz/354364e2a58786e2be71 to your computer and use it in GitHub Desktop.
An AngularJS $http decorator to filter duplicate requests (now released as library: https://github.com/meanie/angular-duplicate-requests-filter)
/**
* Module definition and dependencies
*/
angular.module('Api.DuplicateRequestsFilter.Decorator', [])
/**
* Config
*/
.config(function($provide) {
/**
* Decorator for the $http service
*/
$provide.decorator('$http', function($delegate, $q) {
/**
* Pending requests and local $http var for natural reference
*/
var pendingRequests = {};
var $http = $delegate;
/**
* Hash generator
*/
function hash(str) {
var h = 0;
var strlen = str.length;
if (strlen === 0) {
return h;
}
for (var i = 0, n; i < strlen; ++i) {
n = str.charCodeAt(i);
h = ((h << 5) - h) + n;
h = h & h;
}
return h >>> 0;
}
/**
* Helper to generate a unique identifier for a request
*/
function getRequestIdentifier(config) {
var str = config.method + config.url;
if (config.params && typeof config.params === 'object') {
str += angular.toJson(config.params);
}
if (config.data && typeof config.data === 'object') {
str += angular.toJson(config.data);
}
return hash(str);
}
/**
* Modified $http service
*/
var $duplicateRequestsFilter = function(config) {
//Ignore for this request?
if (config.ignoreDuplicateRequest) {
return $http(config);
}
//Get unique request identifier
var identifier = getRequestIdentifier(config);
//Check if such a request is pending already
if (pendingRequests[identifier]) {
if (config.rejectDuplicateRequest) {
return $q.reject({
data: '',
headers: {},
status: config.rejectDuplicateStatusCode || 400,
config: config
});
}
return pendingRequests[identifier];
}
//Create promise using $http and make sure it's reset when resolved
pendingRequests[identifier] = $http(config).finally(function() {
delete pendingRequests[identifier];
});
//Return promise
return pendingRequests[identifier];
};
//Map rest of methods
Object.keys($http).filter(function(key) {
return (typeof $http[key] === 'function');
}).forEach(function(key) {
$duplicateRequestsFilter[key] = $http[key];
});
//Return it
return $duplicateRequestsFilter;
});
});
@davidsonalencar
Copy link

Very good!!!!

@Kopleman
Copy link

Kopleman commented Dec 7, 2015

While using this i've got a Uncaught TypeError: Cannot read property 'headers' of undefined error whis is quite strange.
Edit: after a little invistigation i have found the problem: .filter(function (key) { return (typeof $http[key] === 'function'); })

@csokun
Copy link

csokun commented Dec 10, 2015

@Kopleman I'm having the same issue. Have you found solution?

@adamreisnz
Copy link
Author

@Kopleman @csokun sorry, didn't see your comments here. Are you still having issues with the directive? I have not encountered that problem. What line was causing that error for you?

@phsacramento
Copy link

How i can use that?

@adamreisnz
Copy link
Author

All you have to do is include the directive in your app and it will take care of the rest automatically. I have since released this as a standalone library so you can install it with npm: https://github.com/meanie/angular-duplicate-requests-filter

@AdamiecRadek
Copy link

AdamiecRadek commented Dec 1, 2016

@adambuczynski I had problem using your code whit custom headers. I have other service that on bootstrap sets my authorization headers and I could not make it working. Also I don't know if that matters but I am using $http object in this way
$http.get('my-super-duper-url, {params:{page: 9000}}).then(function(){});
Here is my solution for double requests (maybe someone in future will have similar problem):

app.config([
  '$provide', function ($provide) {
    $provide.decorator('$http', [
      '$delegate', '$cacheFactory', '$timeout', function ($delegate, $cacheFactory, $timeout) {

        //Create cache object
        var customCache = $cacheFactory('customCache');

        //copy old function for further usage
        var oldGetFunc = angular.copy($delegate.get);


        //Our magic
        function preventDuplicate(url, config) {

          //Create unique identifier for our request
          //TODO: PROBABLY WE ALSO NEED TO TAKE INTO CONSIDERATION HEADERS
          var confString = null;
          if (typeof config == 'object') {
            confString = JSON.stringify(config);
          }

          var requestKey = url + confString;

          //Cache request for one second as this is probably enough to prevent duplicate requests
          var requestPromise = customCache.get(requestKey);
          if (!requestPromise) {
            requestPromise = oldGetFunc(url, config);
            customCache.put(requestKey, requestPromise);
            $timeout(function () {
              customCache.remove(requestKey);
            }, 1000);
          }

          return requestPromise;
        }


        //Change default behavior of GET.
        //Probably it is not good idea to prevent duplicates for other methods
        $delegate.get = function (url, config) {
          return preventDuplicate(url, config)
        };

        return $delegate;
      }
    ]);
  }
]);

This snippet was inspired by your work and this fiddle
http://jsfiddle.net/referee/hh30bh0q/

@adamreisnz
Copy link
Author

@AdamiecRadek thanks for your comment, I haven't had issues myself with the filter in our production apps, and we also use Authorization headers as well as some additional custom headers, so curious to know at what point it went wrong. Glad you found a solution though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment