Skip to content

Instantly share code, notes, and snippets.

@seeruk
Created November 14, 2014 12:23
Show Gist options
  • Save seeruk/6156c03df369239444c6 to your computer and use it in GitHub Desktop.
Save seeruk/6156c03df369239444c6 to your computer and use it in GitHub Desktop.
Angular Caching Service
angular.module("td.service.cache", [ "td.service.timestamp" ])
.factory("CacheService", function($q, TimestampService) {
"use strict";
var CacheService = {},
pendingActions = {},
prefix = "CacheService_";
/**
* Generate a cache key
*
* @param {String} key
*
* @return {String}
*/
function generateKey(key) {
return prefix + key;
}
/**
* Get an item from the cache
*
* @param {String} key
*
* @return {Mixed}
*/
CacheService.get = function(key) {
var deferred = $q.defer();
if (!pendingActions[key]) {
pendingActions[key] = $q.defer();
pendingActions[key].resolve();
}
pendingActions[key].promise.finally(function() {
var item = JSON.parse(localStorage.getItem(generateKey(key)));
if (
item !== null &&
typeof item === "object" &&
item.value &&
item.expires % 1 === 0 &&
(item.expires === 0 || item.expires > TimestampService.unix())
) {
deferred.resolve(item.value);
} else {
deferred.reject();
}
});
pendingActions[key] = deferred;
return deferred.promise;
};
/**
* Set an item in the cache
*
* @param {String} key
* @param {Mixed} value
* @param {Integer} ttl
*
* @return {Mixed}
*/
CacheService.set = function(key, value, ttl) {
if (typeof value === "function") {
return CacheService.set(key, value(), ttl);
}
var deferred = $q.defer();
if (!pendingActions[key]) {
pendingActions[key] = $q.defer();
pendingActions[key].resolve();
}
pendingActions[key].promise.finally(function() {
// Transform value into promise
$q.when(value)
.then(
function(value) {
var expires = (ttl !== undefined) ?
TimestampService.unix() + ttl :
0;
try {
localStorage.setItem(generateKey(key), JSON.stringify({
value: value,
expires: expires
}));
deferred.resolve(value);
return deferred;
} catch (e) {
deferred.reject(e);
if (
e.name === "QuotaExceededError" ||
e.name === "NS_ERROR_DOM_QUOTA_REACHED"
) {
console.warn(
"[CacheService]",
"localStorage storage limit reached. Flushing."
);
CacheService.empty();
} else {
throw e;
}
}
},
function() {
deferred.reject();
}
)
;
});
pendingActions[key] = deferred;
return deferred.promise;
};
/**
* Wrap get/set so that functionality can be easily cached
*
* @param {String} key
* @param {Function} callback
* @param {Integer} ttl
*
* @return {Mixed}
*/
CacheService.proxy = function(key, value, ttl) {
if (value === undefined) {
// @todo: Make exceptional
throw new Error();
}
return CacheService.get(key)
.catch(function() {
return CacheService.set(key, value, ttl);
})
;
};
/**
* Remove an item from the cache
*
* @param {String} key
*/
CacheService.delete = function(key) {
if (pendingActions[key]) {
pendingActions[key].reject();
delete pendingActions[key];
}
localStorage.removeItem(generateKey(key));
return $q.when();
};
/**
* Flush the entire cache
*/
CacheService.empty = function() {
var promises = [];
Object
.keys(localStorage)
.filter(function(key) {
return key.substr(0, prefix.length) === prefix;
})
.map(function(key) {
return key.substr(prefix.length);
})
.concat(Object.keys(pendingActions))
.forEach(function(key) {
promises.push(CacheService.delete(key));
})
;
return $q.all(promises);
};
return CacheService;
})
;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment