cache-helpers
Advanced tools
Comparing version 1.3.0 to 1.4.0
220
index.js
@@ -0,95 +1,157 @@ | ||
"use strict"; | ||
exports.once = function(func) { | ||
var cache = undefined, | ||
callbacks = null | ||
var f, handler; | ||
return function(callback) { | ||
if(cache !== undefined) | ||
return callback(null, cache) | ||
handler = function(callback) { | ||
var list; | ||
if(callbacks) { | ||
callbacks.push(callback) | ||
return | ||
} | ||
list = [callback]; | ||
callbacks = [callback] | ||
f = function(callback) { | ||
list.push(callback); | ||
}; | ||
return func(function(err, data) { | ||
if(!err) | ||
cache = data | ||
func(function(err, result) { | ||
var i; | ||
while(callbacks.length) | ||
callbacks.pop()(err, data) | ||
f = err? | ||
handler: | ||
function(callback) { | ||
process.nextTick(function() { | ||
callback(err, result); | ||
}); | ||
}; | ||
callbacks = null | ||
}) | ||
} | ||
} | ||
for(i = 0; i < list.length; i++) { | ||
list[i](err, result); | ||
} | ||
}); | ||
}; | ||
/* FIXME: why have an inProgress field? just set callbacks to null initially | ||
* have it be an array only when we're in progress... */ | ||
f = handler; | ||
return function(callback) { | ||
f(callback); | ||
}; | ||
}; | ||
exports.timeBasedWithGrace = function(func, soft, hard) { | ||
var cache = undefined, | ||
softLimit = 0, | ||
hardLimit = 0, | ||
inProgress = false, | ||
callbacks = [] | ||
var f, handler; | ||
return function(now, callback) { | ||
/* If we're before the hardlimit, just call the callback immediately with | ||
* cached data. Otherwise, if we're after it, we'll have to defer the | ||
* callback until later on. */ | ||
if(now < hardLimit) | ||
callback(null, cache) | ||
/* "Hard" handler. */ | ||
handler = function(time, callback) { | ||
var list; | ||
else | ||
callbacks.push(callback) | ||
list = [callback]; | ||
/* If there's nothing more to do (because we're before the soft limit or | ||
* because somebody else is already doing it), check out. */ | ||
if(now < softLimit || inProgress) | ||
return | ||
f = function(time, callback) { | ||
list.push(callback); | ||
}; | ||
/* Okay, so no we have to update the cache. Mark that we're doing something | ||
* so that nobody else tries to. */ | ||
inProgress = true | ||
func(function(err, result) { | ||
var hard_time, i, sandler, soft_time; | ||
/* Call the backing function. */ | ||
return func(function(err, data) { | ||
/* If nothing went wrong, update the cache and timeouts. */ | ||
if(!err) { | ||
cache = data | ||
softLimit = now + soft | ||
hardLimit = now + hard | ||
if(err) { | ||
f = handler; | ||
} | ||
/* Call each of the callbacks that we buffered up. (We just pass `err` to | ||
* them because if there was no error, it's just going to be null anyway. | ||
* Vice-versa applies as well.) */ | ||
while(callbacks.length) | ||
callbacks.pop()(err, data) | ||
else { | ||
soft_time = time + soft; | ||
hard_time = time + hard; | ||
/* Finally, the last thing we do is mark that we're no longer in | ||
* progress, since we just finished. */ | ||
inProgress = false | ||
}) | ||
sandler = function(time, callback) { | ||
var list; | ||
if(time >= hard_time) { | ||
handler(time, callback); | ||
} | ||
else { | ||
process.nextTick(function() { | ||
callback(err, result); | ||
}); | ||
if(time >= soft_time) { | ||
list = null; | ||
f = function(time, callback) { | ||
if(time >= hard_time) { | ||
if(!list) { | ||
list = []; | ||
} | ||
list.push(callback); | ||
} | ||
else { | ||
process.nextTick(function() { | ||
callback(err, result); | ||
}); | ||
} | ||
}; | ||
func(function(new_err, new_result) { | ||
var i; | ||
if(!new_err) { | ||
err = new_err; | ||
result = new_result; | ||
soft_time = time + soft; | ||
hard_time = time + hard; | ||
} | ||
f = sandler; | ||
if(list) { | ||
for(i = 0; i < list.length; i++) { | ||
list[i](err, result); | ||
} | ||
} | ||
}); | ||
} | ||
} | ||
}; | ||
f = sandler; | ||
} | ||
for(i = 0; i < list.length; i++) { | ||
list[i](err, result); | ||
} | ||
}); | ||
}; | ||
f = handler; | ||
return function(time, callback) { | ||
f(time, callback); | ||
}; | ||
}; | ||
exports.sizeBasedKeyValue = function(func, size) { | ||
if(process.env["NODE_ENV"] !== "test") { | ||
console.warn( | ||
"cacheHelpers.sizeBasedKeyValue is DEPRECATED and will be removed" | ||
); | ||
} | ||
} | ||
exports.sizeBasedKeyValue = function(func, size) { | ||
var cache = [], | ||
callbacks = {} | ||
callbacks = {}; | ||
size += size | ||
size += size; | ||
return function(key, callback) { | ||
/* Look up in the cache. */ | ||
var i | ||
var i; | ||
for(i = 0; i !== cache.length; i += 2) | ||
for(i = 0; i !== cache.length; i += 2) { | ||
if(cache[i] === key) { | ||
if(i !== 0) | ||
Array.prototype.unshift.apply(cache, cache.splice(i, 2)) | ||
if(i !== 0) { | ||
Array.prototype.unshift.apply(cache, cache.splice(i, 2)); | ||
} | ||
return callback(null, cache[1]) | ||
return callback(null, cache[1]); | ||
} | ||
} | ||
@@ -99,7 +161,7 @@ /* Somebody else is already polling the backend. Get notified when they're | ||
if(callbacks[key]) { | ||
callbacks[key].push(callback) | ||
return | ||
callbacks[key].push(callback); | ||
return; | ||
} | ||
callbacks[key] = [callback] | ||
callbacks[key] = [callback]; | ||
@@ -110,15 +172,17 @@ /* Call the backing store. */ | ||
if(!err) { | ||
if(cache.length === size) | ||
cache.length -= 2 | ||
if(cache.length === size) { | ||
cache.length -= 2; | ||
} | ||
cache.unshift(key, value) | ||
cache.unshift(key, value); | ||
} | ||
/* Notify all the saved callbacks, and then clean up. */ | ||
while(callbacks[key].length) | ||
callbacks[key].pop()(err, value) | ||
while(callbacks[key].length) { | ||
callbacks[key].pop()(err, value); | ||
} | ||
delete callbacks[key] | ||
}) | ||
} | ||
} | ||
delete callbacks[key]; | ||
}); | ||
}; | ||
}; |
{ | ||
"name": "cache-helpers", | ||
"version": "1.3.0", | ||
"version": "1.4.0", | ||
"description": "caching convenience functions", | ||
@@ -19,5 +19,8 @@ "keywords": [ | ||
"devDependencies": { | ||
"mocha": "1.6.x", | ||
"chai": "1.3.x" | ||
"mocha": "*", | ||
"jshint": "*" | ||
}, | ||
"scripts": { | ||
"test": "./node_modules/.bin/jshint *.js && NODE_ENV=test ./node_modules/.bin/mocha test" | ||
} | ||
} |
@@ -54,1 +54,10 @@ cache-helpers | ||
concrete use case. Checking out the tests may help. | ||
License | ||
------- | ||
To the extend possible by law, The Dark Sky Company, LLC has [waived all | ||
copyright and related or neighboring rights][cc0] to this library. | ||
[cc0]: http://creativecommons.org/publicdomain/zero/1.0/ |
308
test.js
@@ -1,128 +0,252 @@ | ||
var assert = require("chai").assert, | ||
cache = require("./") | ||
"use strict"; | ||
var assert, cache; | ||
assert = require("assert"); | ||
cache = require("./"); | ||
describe("cache", function() { | ||
describe("time-based", function() { | ||
var n | ||
var inc, n; | ||
function inc(callback) { | ||
return callback(null, n++) | ||
} | ||
inc = function(callback) { | ||
process.nextTick(function() { | ||
callback(null, n++); | ||
}); | ||
}; | ||
beforeEach(function() { | ||
n = 0 | ||
}) | ||
beforeEach(function() { | ||
n = 0; | ||
}); | ||
describe("once", function() { | ||
it("should only call the backing cache function once", function() { | ||
var cinc = cache.once(inc) | ||
describe("once", function() { | ||
it("should only call the backing cache function once", function(done) { | ||
var cinc = cache.once(inc); | ||
cinc(function(err, n) { | ||
assert.ifError(err); | ||
assert.equal(n, 0); | ||
cinc(function(err, n) { | ||
assert.equal(n, 0) | ||
}) | ||
assert.ifError(err); | ||
assert.equal(n, 0); | ||
cinc(function(err, n) { | ||
assert.ifError(err); | ||
assert.equal(n, 0); | ||
done(null); | ||
}); | ||
}); | ||
}); | ||
}); | ||
cinc(function(err, n) { | ||
assert.equal(n, 0) | ||
}) | ||
it("should call the backing cache function once even if simultaneous " + | ||
"calls are made", function(done) { | ||
var cinc = cache.once(inc), | ||
m = 0; | ||
cinc(function(err, n) { | ||
assert.equal(n, 0) | ||
}) | ||
}) | ||
cinc(function(err, n) { | ||
assert.ifError(err); | ||
assert.equal(n, 0); | ||
if(++m === 3) { | ||
done(null); | ||
} | ||
}); | ||
/* FIXME: Ensure that calling the function a bajillion times causes each | ||
* callback to get called. */ | ||
}) | ||
cinc(function(err, n) { | ||
assert.ifError(err); | ||
assert.equal(n, 0); | ||
if(++m === 3) { | ||
done(null); | ||
} | ||
}); | ||
describe("timeBasedWithGrace", function() { | ||
it("should only update the cache as appropriate", function() { | ||
var cinc = cache.timeBasedWithGrace(inc, 100, 1000) | ||
cinc(function(err, n) { | ||
assert.ifError(err); | ||
assert.equal(n, 0); | ||
if(++m === 3) { | ||
done(null); | ||
} | ||
}); | ||
}); | ||
}); | ||
/* First datapoint. */ | ||
cinc(0, function(err, n) { | ||
assert.deepEqual(n, 0) | ||
}) | ||
describe("timeBasedWithGrace", function() { | ||
it("should only update the cache as appropriate", function(done) { | ||
var cinc = cache.timeBasedWithGrace(inc, 100, 1000); | ||
/* First datapoint. */ | ||
cinc(0, function(err, n) { | ||
assert.ifError(err); | ||
assert.deepEqual(n, 0); | ||
/* Read cached. */ | ||
cinc(50, function(err, n) { | ||
assert.deepEqual(n, 0) | ||
}) | ||
assert.ifError(err); | ||
assert.deepEqual(n, 0); | ||
/* Soft timeout: return cached but update in the background. */ | ||
/* Soft timeout: return cached but update in the background. */ | ||
cinc(200, function(err, n) { | ||
assert.ifError(err); | ||
assert.deepEqual(n, 0); | ||
/* Read cached. */ | ||
cinc(210, function(err, n) { | ||
assert.ifError(err); | ||
assert.deepEqual(n, 1); | ||
/* Hard timeout: update n before returning. */ | ||
cinc(2500, function(err, n) { | ||
assert.ifError(err); | ||
assert.deepEqual(n, 2); | ||
done(null); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
it("should only call the callback once if called multiple times " + | ||
"simultaneously", function(done) { | ||
var cinc, m; | ||
cinc = cache.timeBasedWithGrace(inc, 100, 1000); | ||
m = 0; | ||
cinc(0, function(err, n) { | ||
assert.ifError(err); | ||
assert.strictEqual(n, 0); | ||
if(++m === 3) { | ||
done(null); | ||
} | ||
}); | ||
cinc(0, function(err, n) { | ||
assert.ifError(err); | ||
assert.strictEqual(n, 0); | ||
if(++m === 3) { | ||
done(null); | ||
} | ||
}); | ||
cinc(0, function(err, n) { | ||
assert.ifError(err); | ||
assert.strictEqual(n, 0); | ||
if(++m === 3) { | ||
done(null); | ||
} | ||
}); | ||
}); | ||
it("should only call the callback once if called multiple times " + | ||
"simultaneously in soft timeout state", function(done) { | ||
var cinc; | ||
cinc = cache.timeBasedWithGrace(inc, 100, 1000); | ||
cinc(0, function(err, n) { | ||
var m; | ||
assert.ifError(err); | ||
assert.strictEqual(n, 0); | ||
m = 0; | ||
cinc(200, function(err, n) { | ||
assert.deepEqual(n, 0) | ||
}) | ||
assert.ifError(err); | ||
assert.strictEqual(n, 0); | ||
if(++m === 3) { | ||
done(null); | ||
} | ||
}); | ||
/* Read cached. */ | ||
cinc(250, function(err, n) { | ||
assert.deepEqual(n, 1) | ||
}) | ||
/* We assume that by the time this gets executed, the background update | ||
* succeeded... */ | ||
cinc(200, function(err, n) { | ||
assert.ifError(err); | ||
assert.strictEqual(n, 1); | ||
if(++m === 3) { | ||
done(null); | ||
} | ||
}); | ||
/* Hard timeout: update n before returning. */ | ||
cinc(2500, function(err, n) { | ||
assert.deepEqual(n, 2) | ||
}) | ||
}) | ||
cinc(200, function(err, n) { | ||
assert.ifError(err); | ||
assert.strictEqual(n, 1); | ||
if(++m === 3) { | ||
done(null); | ||
} | ||
}); | ||
}); | ||
}); | ||
/* FIXME: Ensure that calling the function a bajillion times causes each | ||
* callback to get called. */ | ||
}) | ||
}) | ||
/* FIXME: Ensure that calling the function a bajillion times causes each | ||
* callback to get called. */ | ||
}); | ||
describe("key-value", function() { | ||
var n | ||
describe("sizeBasedKeyValue", function() { | ||
var inc, n; | ||
function inc(key, callback) { | ||
return callback(null, key ^ n++) | ||
} | ||
inc = function(key, callback) { | ||
process.nextTick(function() { | ||
callback(null, key ^ n++); | ||
}); | ||
}; | ||
beforeEach(function() { | ||
n = 0 | ||
}) | ||
n = 0; | ||
}); | ||
describe("sizeBasedKeyValue", function() { | ||
it("should cache for each key, until you fill the space", function() { | ||
var f = cache.sizeBasedKeyValue(inc, 2) | ||
it("should cache for each key, until you fill the space", function(done) { | ||
var f = cache.sizeBasedKeyValue(inc, 2); | ||
f(20, function(err, value) { | ||
assert.equal(value, 20) | ||
}) | ||
f(20, function(err, value) { | ||
assert.ifError(err); | ||
assert.equal(value, 20); | ||
f(20, function(err, value) { | ||
assert.equal(value, 20) | ||
}) | ||
assert.ifError(err); | ||
assert.equal(value, 20); | ||
f(40, function(err, value) { | ||
assert.equal(value, 41) | ||
}) | ||
f(40, function(err, value) { | ||
assert.ifError(err); | ||
assert.equal(value, 41); | ||
f(20, function(err, value) { | ||
assert.equal(value, 20) | ||
}) | ||
f(20, function(err, value) { | ||
assert.ifError(err); | ||
assert.equal(value, 20); | ||
f(40, function(err, value) { | ||
assert.equal(value, 41) | ||
}) | ||
f(40, function(err, value) { | ||
assert.ifError(err); | ||
assert.equal(value, 41); | ||
f(30, function(err, value) { | ||
assert.equal(value, 28) | ||
}) | ||
f(30, function(err, value) { | ||
assert.ifError(err); | ||
assert.equal(value, 28); | ||
f(40, function(err, value) { | ||
assert.equal(value, 41) | ||
}) | ||
f(40, function(err, value) { | ||
assert.ifError(err); | ||
assert.equal(value, 41); | ||
f(20, function(err, value) { | ||
assert.equal(value, 23) | ||
}) | ||
f(20, function(err, value) { | ||
assert.ifError(err); | ||
assert.equal(value, 23); | ||
f(40, function(err, value) { | ||
assert.equal(value, 41) | ||
}) | ||
}) | ||
f(40, function(err, value) { | ||
assert.ifError(err); | ||
assert.equal(value, 41); | ||
/* FIXME: Ensure that calling the function a bajillion times causes each | ||
* callback to get called. */ | ||
}) | ||
}) | ||
}) | ||
done(null); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
/* FIXME: Ensure that calling the function a bajillion times causes each | ||
* callback to get called. */ | ||
}); | ||
}); |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
13691
6
349
63
1