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 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 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