memoize-fs
memoize/cache in file system solution for Node.js
Motivation
This project is inspired by the memoize project by Mariusz Nowak aka medikoo.
The motivation behind this module is that sometimes you have to persist cached function calls but you do not want to deal with an extra process (ie. managing a Redis store).
Memoization is best technique to save on memory or CPU cycles when we deal with repeated operations. For detailed insight see: http://en.wikipedia.org/wiki/Memoization
Features
Installation
In your project path:
npm install memoize-fs --save
Usage
var memoize = require('memoize-fs')({ cachePath: require('path').join(__dirname, '../../cache' }),
fun = function (a, b) { return a + b; };
memoize.fn(fun).then(function (memFn) {
memFn(1, 2).then(function (result) {
assert.strictEqual(result, 3);
memFn(1, 2).then(function (result) {
assert.strictEqual(result, 3);
}, function (err) { });
}, function (err) { });
}, function (err) { });
Note that a result of a momoized function is always a Promise instance!
Memoizing asynchronous functions
memoise-fs assumes a function asynchronous if the last argument it accepts is of type function
and that function itself accepts at least one argument.
So basically you don't have to do anything differently than when memoizing synchronous functions. Just make sure the above condition is fulfilled.
Here is an example of memoizing a function with a callback:
var funAsync = function (a, b, cb) {
setTimeout(function () {
cb(null, a + b);
}, 100);
};
memoize.fn(funAsync).then(function (memFn) {
memFn(1, 2, function (err, sum) { if (err) { throw err; } console.log(sum); }).then(function () {
memFn(1, 2, function (err, sum) { if (err) { throw err; } console.log(sum); }).then(function () {
}, function (err) { });
}, function (err) { });
}, function (err) { });
Memoizing promisified functions
You can also memoize a promisified function. memoize-fs assumes a function promisified if its result is thenable
which means that the result is an object with a property then
of type function
(read more about JavaScript promises here).
So again it's the same as with memoizing synchronous functions.
Here is an example of memoizing a promisified function:
var funPromisified = function (a, b) {
return new require('es6-promise').Promise(function (resolve, reject) {
setTimeout(function () { resolve(a + b); }, 100);
});
};
memoize.fn(funPromisified).then(function (memFn) {
memFn(1, 2).then(function (result) {
assert.strictEqual(result, 3);
memFn(1, 2).then(function (result) {
assert.strictEqual(result, 3);
}, function (err) { });
}, function (err) { });
}, function (err) { });
Options
When memoizing a function all below options can be applied in any combination.
cacheId
By default all cache files are saved into the root cache which is the folder specified by the cachePath option:
var memoize = require('memoize-fs')({ cachePath: require('path').join(__dirname, '../../cache' });
The cacheId option which you can specify during momoization of a function resolves to the name of a subfolder created inside the root cache folder. Cached function calls will be cached inside that folder:
memoize.fn(fun, { cacheId: 'foobar'}).then(...
salt
Functions may have references to variables outside their own scope. As a consequence two functions which look exactly the same (they have the same function signature and function body) can return different results even when executed with identical arguments. In order to avoid the same cache being used for two different functions you can use the salt option which mutates the hash key created for the memoized function which in turn defines the name of the cache file:
memoize.fn(fun, { salt: 'foobar'}).then(...
force
The force option forces the re-execution of an already memoized function and the re-caching of its outcome:
memoize.fn(fun, { force: true}).then(...
serialize
memoize-fs tries to serialize the arguments of the memoized function in order to create a hash which is used as the name of the cache file to be stored or retrieved.
If you want memoize-fs to use a custom key instead of letting it serialize the arguments, you can pass the key in the serialize option to memoize:
memoize.fn(fun, { serialize: 'foobar'}).then(...
Alternatively you can pass another object to be serialized in place of the arguments of the memoized function:
memoize.fn(fun, { serialize: { foo: 'bar'}}).then(...
Manual cache invalidation
You can delete the root cache (all cache files inside the folder specified by the cachePath option):
memoize.invalidate().then(...
You can also pass the cacheId argument to the invalidate method. This way you only delete the cache inside the subfolder with given id.
memoize.invalidate('foobar').then(...
Common pitfalls
-
Be carefull when memoizing a function which uses variables from the outer scope.
The value of these variables may change during runtime but the cached result will remain the same
when calling the memoized function with the same arguments as the first time when the result was cached.
-
Be careful when memoizing a function which excepts arguments which are of type function
or have attributes of type function
.
These arguments will be ignored silently during serialization.
To avoid flawy caching please use custom serialization.
Contributing
Issues and Pull-requests are absolutely welcome. If you want to submit a patch, please make sure that you follow this simple rule:
All code in any code-base should look like a single person typed it, no matter how
many people contributed. — idiomatic.js
Lint with:
npm run jshint
Test with:
npm run mocha
Check code coverage with:
npm run istanbul
Then please commit with a detailed commit message.