memoize-fs
Advanced tools
Comparing version 0.0.5 to 0.1.0
127
index.js
@@ -30,7 +30,2 @@ 'use strict'; | ||
function checkOptions(optExt) { | ||
if (optExt.salt && typeof optExt.salt !== 'string') { throw new Error('salt option of type string expected, got \'' + typeof optExt.salt + '\''); } | ||
if (optExt.cacheId && typeof optExt.cacheId !== 'string') { throw new Error('cacheId option of type string expected, got \'' + typeof optExt.cacheId + '\''); } | ||
} | ||
function getCacheFilePath(fn, args, opt) { | ||
@@ -45,3 +40,3 @@ | ||
if (typeof value === 'function') { | ||
return; | ||
return; // ignore arguments and attributes of type function silently | ||
} | ||
@@ -78,2 +73,8 @@ if (typeof value === 'object' && value !== null) { | ||
function memoizeFn(fn, opt) { | ||
function checkOptions(optExt) { | ||
if (optExt.salt && typeof optExt.salt !== 'string') { throw new Error('salt option of type string expected, got \'' + typeof optExt.salt + '\''); } | ||
if (optExt.cacheId && typeof optExt.cacheId !== 'string') { throw new Error('cacheId option of type string expected, got \'' + typeof optExt.cacheId + '\''); } | ||
} | ||
if (opt && typeof opt !== 'object') { throw new Error('opt of type object expected, got \'' + typeof opt + '\''); } | ||
@@ -91,3 +92,9 @@ | ||
var memFn = function () { | ||
var args = arguments; | ||
var args = arguments, | ||
fnaCb = _.last(args); | ||
if (typeof fnaCb === 'function' && fnaCb.length > 0) { | ||
optExt.async = true; | ||
} | ||
return new Promise(function (resolve, reject) { | ||
@@ -98,6 +105,2 @@ /* jshint unused: vars */ | ||
fs.readFile(filePath, { encoding: 'utf8' }, function (err, data) { | ||
var result, | ||
resultArr, | ||
resultType, | ||
resultStr; | ||
@@ -129,35 +132,83 @@ function stringifyResult(r) { | ||
if (err || optExt.force) { | ||
delete optExt.force; | ||
// result has not been cached yet or needs to be recached - cache and return it! | ||
try { | ||
result = fn.apply(null, args); | ||
} catch (e) { | ||
return reject(e); | ||
} | ||
if (result && result.then) { | ||
// result is a promise instance | ||
return result.then(function (retObj) { | ||
fs.writeFile(filePath, typeof retObj + '\n' + stringifyResult(retObj)); // async without callback! | ||
resolve(retObj); | ||
}, | ||
function (err) { | ||
function cacheAndReturn() { | ||
var result, | ||
resultStr; | ||
function processFnAsync() { | ||
var fnaArgs = _.initial(args), | ||
fnaCb = _.last(args); | ||
fnaArgs.push(function(/* err, result... */) { | ||
var cbErr = _.first(arguments), | ||
cbArgs = _.rest(arguments); | ||
if (cbErr) { | ||
// if we have an exception we don't cache anything | ||
reject(err); | ||
}); | ||
} else { | ||
resultStr = stringifyResult(result); | ||
fs.writeFile(filePath, typeof result + '\n' + resultStr, function (err) { | ||
if (err) { | ||
reject(err); | ||
} else { | ||
resolve(result); | ||
return reject(cbErr); | ||
} | ||
cbArgs.unshift(null); | ||
fs.writeFile(filePath, 'object' + '\n' + stringifyResult(cbArgs)); // async without callback! | ||
resolve(fnaCb.apply(null, cbArgs)); | ||
}); | ||
fn.apply(null, fnaArgs); | ||
} | ||
function processFn() { | ||
try { | ||
result = fn.apply(null, args); | ||
} catch (e) { | ||
return reject(e); | ||
} | ||
if (result && result.then && typeof result.then === 'function') { | ||
// result is a promise instance | ||
return result.then(function (retObj) { | ||
fs.writeFile(filePath, typeof retObj + '\n' + stringifyResult(retObj)); // async without callback! | ||
resolve(retObj); | ||
}, | ||
function (err) { | ||
// if we have an exception we don't cache anything | ||
reject(err); | ||
}); | ||
} else { | ||
resultStr = stringifyResult(result); | ||
fs.writeFile(filePath, typeof result + '\n' + resultStr, function (err) { | ||
if (err) { | ||
reject(err); | ||
} else { | ||
resolve(result); | ||
} | ||
}); | ||
} | ||
} | ||
if (optExt.async) { | ||
return processFnAsync(); | ||
} | ||
return processFn(); | ||
} | ||
function retrieveAndReturn() { | ||
var resultArr = data.split('\n'); | ||
function processFnAsync() { | ||
var fnaCb = _.last(args); | ||
resolve(fnaCb.apply(null, parseResult(_.rest(resultArr).join('\n'), _.first(resultArr)))); | ||
} | ||
function processFn() { | ||
resolve(parseResult(_.rest(resultArr).join('\n'), _.first(resultArr))); | ||
} | ||
if (optExt.async) { | ||
return processFnAsync(); | ||
} | ||
return processFn(); | ||
} | ||
if (err || optExt.force) { | ||
delete optExt.force; | ||
// result has not been cached yet or needs to be recached - cache and return it! | ||
cacheAndReturn(); | ||
} else { | ||
// result has already been cached - return it! | ||
resultArr = data.split('\n'); | ||
resultType = _.first(resultArr); | ||
resolve(parseResult(_.rest(resultArr).join('\n'), resultType)); | ||
retrieveAndReturn(); | ||
} | ||
@@ -164,0 +215,0 @@ }); |
{ | ||
"name": "memoize-fs", | ||
"version": "0.0.5", | ||
"version": "0.1.0", | ||
"description": "memoize/cache in file system solution for Node.js", | ||
@@ -11,4 +11,7 @@ "author": { | ||
"scripts": { | ||
"test": "mocha --reporter nyan test", | ||
"cover": "rm -rf coverage && ./node_modules/.bin/istanbul cover ./node_modules/mocha/bin/_mocha --report html && open coverage/index.html" | ||
"test": "mocha test && grunt", | ||
"mocha": "mocha --reporter nyan test", | ||
"jshint": "grunt", | ||
"istanbul": "rm -rf coverage && ./node_modules/.bin/istanbul cover ./node_modules/mocha/bin/_mocha --report html && open coverage/memoize-fs/index.js.html", | ||
"coveralls": "rm -rf coverage && ./node_modules/.bin/istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage" | ||
}, | ||
@@ -37,3 +40,6 @@ "homepage": "https://github.com/borisdiakur/memoize-fs", | ||
"coveralls": "~2.10.0", | ||
"mocha-lcov-reporter": "0.0.1" | ||
"mocha-lcov-reporter": "0.0.1", | ||
"grunt": "~0.4.5", | ||
"grunt-contrib-jshint": "~0.10.0", | ||
"grunt-cli": "~0.1.13" | ||
}, | ||
@@ -40,0 +46,0 @@ "private": false, |
@@ -13,2 +13,3 @@ # memoize-fs | ||
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 | ||
@@ -19,3 +20,4 @@ | ||
* Works with almost all kind and any length of function arguments – [__custom serialization is posible__](#serialize) | ||
* Support for [__promisified functions__](#memoizing-promisified-functions) | ||
* Supports memoization of [__asynchronous functions__](#memoizing-asynchronous-functions) | ||
* Supports memoization of [__promisified functions__](#memoizing-promisified-functions) | ||
* Cache [__can be invalidated manually__](#manual-cache-invalidation) | ||
@@ -27,3 +29,5 @@ | ||
$ npm install memoize-fs | ||
```shell | ||
npm install memoize-fs --save | ||
``` | ||
@@ -48,18 +52,31 @@ ## Usage | ||
### Memoizing promisified functions | ||
### Memoizing asynchronous functions | ||
In order to memoize an async function it must be first promisified in the following manner: | ||
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: | ||
Before: | ||
```javascript | ||
var funAsync = function (a, b, cb) { setTimeout(function () { cb(null, a + b); }, 100); }; | ||
var funAsync = function (a, b, cb) { | ||
setTimeout(function () { | ||
cb(null, a + b); | ||
}, 100); | ||
}; | ||
// later | ||
funAsync(1, 2, function (err, result) { | ||
if (err) throw err; | ||
console.log(result); | ||
}); | ||
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 () { // cache hit | ||
// callback is called with previously cached arguments | ||
}, function (err) { /* handle error */ }); | ||
}, function (err) { /* handle error */ }); | ||
}, function (err) { /* handle error */ }); | ||
``` | ||
After: | ||
### 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](http://www.html5rocks.com/en/tutorials/es6/promises/?redirect_from_locale=de)). | ||
So again it's the same as with memoizing synchronous functions. | ||
Here is an example of memoizing a promisified function: | ||
```javascript | ||
@@ -72,11 +89,10 @@ var funPromisified = function (a, b) { | ||
// later | ||
funPromisified(1, 2).then(function (result) { | ||
console.log(result); | ||
}, function (err) { | ||
throw err; | ||
}); | ||
// now we can memoize it | ||
memoize.fn(funPromisified).then(... | ||
memoize.fn(funPromisified).then(function (memFn) { | ||
memFn(1, 2).then(function (result) { | ||
assert.strictEqual(result, 3); | ||
memFn(1, 2).then(function (result) { // cache hit | ||
assert.strictEqual(result, 3); | ||
}, function (err) { /* handle error */ }); | ||
}, function (err) { /* handle error */ }); | ||
}, function (err) { /* handle error */ }); | ||
``` | ||
@@ -138,3 +154,3 @@ | ||
```javascript | ||
memoized.invalidate().then(... | ||
memoize.invalidate().then(... | ||
``` | ||
@@ -145,14 +161,44 @@ | ||
```javascript | ||
memoized.invalidate('foobar').then(... | ||
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__](#serialize). | ||
## Contributing | ||
Issues and Pull-requests welcome. If you want to submit a patch, please make sure that you follow this simple rule: | ||
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](https://github.com/rwldrn/idiomatic.js/) | ||
many people contributed. — [idiomatic.js](https://github.com/rwldrn/idiomatic.js/) | ||
## Change Log | ||
Lint with: | ||
```shell | ||
npm run jshint | ||
``` | ||
Test with: | ||
```shell | ||
npm run mocha | ||
``` | ||
Check code coverage with: | ||
```shell | ||
npm run istanbul | ||
``` | ||
Then please commit with a __detailed__ commit message. | ||
## Change log | ||
v0.1.0 - memoization of [asynchronous functions](#memoizing-asynchronous-functions) | ||
v0.0.5 - coveralls and mocha-lcov-reporter are dev-dependencies | ||
@@ -159,0 +205,0 @@ |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
23253
224
206
7
2