Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Caching HTTP requests with Angular
angular.module('MyApp').factory('UrlCacheInterceptor',
function () {
'use strict';
/**
* Returns value indicating whether required
* features are supported by the browser.
*/
var featuresSupported = function () {
return ['URL', 'Blob', 'WeakMap'].every(function (feature) {
return typeof (window[feature]) !== 'undefined';
});
};
return featuresSupported() ? (function () {
var cache = {
/* Stores cache rules. */
rules: {
/**
* Returns cache rule that corresponds to the given HTTP request.
* @param config {object} HTTP request configuration.
*/
get: function (config) {
return cache.rules.contents.filter(function (rule) {
return config.url === rule.url &&
config.method.toLowerCase() ===
(rule.method || 'get').toLowerCase();
}).pop();
},
/* Stores cache rule contents. */
contents: [
{ url: '/profile' },
{ url: '/logout', method: 'post', onInsert: function () {
/* On logout, clearing the entire cache. */
cache.clear();
/* No need to cache this HTTP response. */
return true;
} }
]
},
/* Stores cache contents. */
contents: new WeakMap(),
/**
* Returns cache data that corresponds to the given rule.
* @param rule {object} Cache rule.
*/
get: function (rule) {
return rule ? cache.contents.get(rule) : null;
},
/**
* Inserts cache data that corresponds to the given rule.
* @param rule {object} Cache rule.
* @param data {object} Cache data.
*/
set: function (rule, data) {
/* Binding to the document via in-memory URL. */
var url = URL.createObjectURL(
new Blob([
JSON.stringify(data)
], { type: 'application/json' }
)
);
cache.contents.set(rule, url);
return url;
},
/**
* Removes cache data that corresponds to the given rule.
* @param rule {object} Cache rule.
*/
remove: function (rule) {
var url = cache.contents.get(rule);
if (url) {
URL.revokeObjectURL(url);
cache.contents.delete(rule);
}
return url;
},
/**
* Clears all the data from the cache.
*/
clear: function () {
cache.rules.contents.forEach(cache.remove);
}
};
return {
/**
* Intercepts the given HTTP request.
* @param config {object} HTTP request configuration.
*/
request: function (config) {
/* Getting the in-memory URL that corresponds to cached data. */
var url = cache.get(cache.rules.get(config));
if (url) {
/* Overriding request URL - no need to go to the server. */
config.url = url;
}
return config;
},
/**
* Intercepts the given HTTP response.
* @param response {object} HTTP response.
*/
response: function (response) {
/* Getting cache rule for the given HTTP request. */
var rule = cache.rules.get(response.config);
/* Not caching same request multiple times. */
if (rule && !cache.get(rule)) {
/* Allowing custom logic to execute before insertion. */
if (!rule.onInsert || !rule.onInsert(response)) {
/* Caching response data. */
cache.set(rule, response.data);
}
}
}
};
})() : {};
}
);
@szymko

This comment has been minimized.

Copy link

szymko commented Aug 31, 2015

hey, that's cool, I just have one question though. Why is WeakMap used here? Is there any chance that one of rules will be garbage collected?

@volpav

This comment has been minimized.

Copy link
Owner Author

volpav commented Sep 30, 2015

Hey @szymko, just noticed your comment ;-) In this particular case, rules will not be GC'ed since they're being held by cache.contents which is captured within the closure (which is part of the singleton).

I agree that if you want your rules to be dynamically populated, WeakMap should probably be substituted with something else. I just wanted to get some sort of hash-table without writing any code for hashing on the key which is object (you could, of course, just use {} but then you better provide a contract for a hash function which felt like over-complicating things and driving away from the main point).

Cheers!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.