activity-stream/lib/Memoizer.js

139 строки
3.5 KiB
JavaScript

/* globals Services, Task */
"use strict";
const {Cu} = require("chrome");
const simplePrefs = require("sdk/simple-prefs");
const ss = require("sdk/simple-storage");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Task.jsm");
const concat = String.prototype.concat;
function Memoizer() {
this._cacheEnabled = simplePrefs.prefs["query.cache"];
if (!ss.storage.queryCache) {
ss.storage.queryCache = {};
}
this._onPrefChange = this._onPrefChange.bind(this);
simplePrefs.on("", this._onPrefChange);
}
Memoizer.prototype = {
/**
* Return a function that will wrap a given function and cache its results.
*
* @param {String} key
* a key to store/retrieve cached values
*
* @param {Function} func
* a function to wrap for caching
*
* @return {Function}
* A function which returns a Promise when called (with data returned as it resolves)
*/
memoize(key, func) {
return (...args) => {
// figure out if we need to replace the cache for this query
let replace = false;
args = args.filter(arg => {
if (typeof arg === "object" && "replace" in arg) {
replace = arg.replace;
return false;
}
return true;
});
// allow object params to be passed, as is expected of query functions
let repr = this._makeRepr(args);
if (this._cacheEnabled && !replace && this.hasRepr(key, repr)) {
Services.obs.notifyObservers(null, `${key}-cache`, "hit");
return new Promise(resolve => resolve(this.getData(key, repr)));
} else {
Services.obs.notifyObservers(null, `${key}-cache`, "miss");
return Task.spawn(function*() {
let data = yield func.apply(this, args);
this.cacheData(key, repr, data);
return data;
}.bind(this));
}
};
},
/**
* Returns a string representation of parameters to be used as a cache sub-key
*/
_makeRepr(args) {
let parts = [];
for (let arg of args) {
if (typeof arg === "object") {
// sort so we have a stable key
let props = Object.keys(arg).sort();
for (let prop of props) {
parts.push(`${prop}:${arg[prop]}`);
}
} else {
parts.push(arg);
}
}
return concat.call(parts);
},
/**
* Stores data in the cache
*
* @param {String} key
* A key for values to be stored
* @param {String} repr
* A sub-key for cached values
* @param {Object} data
* JSON serializable data to cache
*/
cacheData(key, repr, data) {
if (!ss.storage.queryCache[key]) {
ss.storage.queryCache[key] = {};
}
ss.storage.queryCache[key][repr] = data;
},
getData(key, repr) {
if (this.hasRepr(key, repr)) {
return ss.storage.queryCache[key][repr];
}
return null;
},
/**
* Returns whether or not the cache contains a key/parameter combination
*
* @returns {Boolean} presence of key/parameter in cache
*/
hasRepr(key, repr) {
return key in ss.storage.queryCache && repr in ss.storage.queryCache[key];
},
invalidateMemos(memoKeys) {
for (let key of memoKeys) {
delete ss.storage.queryCache[key];
}
},
reset() {
ss.storage.queryCache = {};
},
_onPrefChange(prefName) {
if (prefName === "query.cache") {
this._cacheEnabled = simplePrefs.prefs["query.cache"];
}
},
uninit() {
simplePrefs.off("", this._onPrefChange);
}
};
exports.Memoizer = Memoizer;