Modularization. Cleanup and logic improvements.

Additionally changed max option algorithm from FIFO to LRU
This commit is contained in:
Mariusz Nowak
2012-09-21 12:04:55 +02:00
parent cdf74cdd37
commit 146e59e543
27 changed files with 2342 additions and 581 deletions

View File

@@ -3,46 +3,111 @@
// Simple benchmark for very simple memoization case (fibonacci series)
// To run it, do following in memoizee package path:
//
// $ npm install bench underscore lodash lru-cache
// $ npm install underscore lodash lru-cache
// $ node benchmark/fibonacci.js
var getFib = function (memoize, opts) {
var forEach = require('es5-ext/lib/Object/for-each')
, pad = require('es5-ext/lib/String/prototype/pad')
, memoizee = require('../lib')
, underscore = require('underscore').memoize
, lodash = require('lodash').memoize
, lruCache = require('lru-cache')
, now = Date.now
, time, getFib, lru, memo, total, index = 3000, count = 10, i, lruMax = 1000
, data = {}, lruObj;
getFib = function (memoize, opts) {
var fib = memoize(function (x) {
return (x < 2) ? 1 : fib(x - 1) + fib(x - 2);
}, opts);
return fib;
};
var memoizee = getFib(require('../lib/memoize'), { primitive: true })
, underscore = getFib(require('underscore').memoize)
, lodash = getFib(require('lodash').memoize)
, index = 200;
var lruCache = require('lru-cache')({});
var lru = function (x) {
var value = lruCache.get(x);
lru = function (x) {
var value = lruObj.get(x);
if (value === undefined) {
value = ((x < 2) ? 1 : lru(x - 1) + lru(x - 2));
lruCache.set(x, value);
lruObj.set(x, value);
}
return value;
};
exports.compare = {
"Underscore": function () {
underscore(index);
},
"Lodash": function () {
lodash(index);
},
"Memoizee": function () {
memoizee(index);
},
"LruCache": function () {
lru(index);
}
};
console.log("Fibonacci", index, "x" + count + ":\n");
require('bench').runMain();
total = 0;
i = count;
while (i--) {
memo = getFib(memoizee);
time = now();
memo(index);
total += now() - time;
}
data["Memoizee (object mode)"] = total;
total = 0;
i = count;
while (i--) {
memo = getFib(memoizee, { primitive: true });
time = now();
memo(index);
total += now() - time;
}
data["Memoizee (primitive mode)"] = total;
total = 0;
i = count;
while (i--) {
memo = getFib(underscore);
time = now();
memo(index);
total += now() - time;
}
data["Underscore"] = total;
total = 0;
i = count;
while (i--) {
memo = getFib(lodash);
time = now();
memo(index);
total += now() - time;
}
data["Lo-dash"] = total;
total = 0;
i = count;
while (i--) {
memo = getFib(memoizee, { primitive: true, max: lruMax });
time = now();
memo(index);
total += now() - time;
}
data["Memoizee (primitive mode) LRU (max: 1000)"] = total;
total = 0;
i = count;
while (i--) {
memo = getFib(memoizee, { max: lruMax });
time = now();
memo(index);
total += now() - time;
}
data["Memoizee (object mode) LRU (max: 1000)"] = total;
total = 0;
i = count;
while (i--) {
lruObj = lruCache({ max: lruMax });
time = now();
lru(index);
total += now() - time;
}
data["lru-cache LRU (max: 1000)"] = total;
forEach(data, function (value, name, obj, index) {
console.log(index + 1 + ":", pad.call(value, " ", 5) + "ms ", name);
}, null, function (a, b) {
return this[a] - this[b];
});

52
lib/_base.js Normal file
View File

@@ -0,0 +1,52 @@
'use strict';
var callable = require('es5-ext/lib/Object/valid-callable')
, forEach = require('es5-ext/lib/Object/for-each')
, ee = require('event-emitter')
, ext;
module.exports = exports = function (core) {
return function self(fn/*, options */) {
var options, length, get, clear, conf;
callable(fn);
options = Object(arguments[1]);
conf = ee({ memoize: self, fn: fn });
// Normalize length
if (isNaN(options.length)) {
length = fn.length;
// Special case
if (options.async && ext.async) {
--length;
}
} else {
length = (options.length === false) ? false : (options.length >>> 0);
}
core(conf, length);
forEach(ext, function (fn, name) {
if (fn.force) {
fn(conf, options);
} else if (options[name]) {
fn(options[name], conf, options);
}
});
fn = conf.fn;
get = conf.get;
clear = conf.clear;
conf.memoized.clear = function () { clear(get(arguments)); };
conf.memoized.clearAll = function () {
conf.emit('purgeall');
conf.clearAll();
};
conf.emit('ready');
return conf.memoized;
};
};
ext = exports.ext = {};

92
lib/ext/async.js Normal file
View File

@@ -0,0 +1,92 @@
'use strict';
var toArray = require('es5-ext/lib/Array/from')
, last = require('es5-ext/lib/Array/prototype/last')
, forEach = require('es5-ext/lib/Object/for-each')
, isCallable = require('es5-ext/lib/Object/is-callable')
, nextTick = require('next-tick')
, isArray = Array.isArray, slice = Array.prototype.slice
, apply = Function.prototype.apply;
require('../_base').ext.async = function (ignore, conf, options) {
var cache;
cache = conf.async = {};
(function (org) {
var value, cb;
conf.on('init', function (id) {
value.id = id;
cache[id] = cb ? [cb] : [];
});
conf.on('hit', function (id) {
if (!cb) {
return;
}
if (isArray(cache[id])) {
cache[id].push(cb);
} else {
nextTick(apply.bind(cb, cache[id].context, cache[id]));
}
});
conf.fn = function () {
var id, args, asyncArgs;
args = arguments;
asyncArgs = toArray(args);
asyncArgs.push(value = function self(err) {
var i, cb, waiting, res;
if (self.id == null) {
// Shouldn't happen, means async callback was called sync way
nextTick(apply.bind(self, this, arguments));
return;
}
waiting = cache[self.id];
if (err) {
conf.clear(self.id);
} else {
arguments.context = this;
cache[self.id] = arguments;
}
for (i = 0; cb = waiting[i]; ++i) {
res = apply.call(cb, this, arguments);
}
return res;
});
return apply.call(org, this, asyncArgs);
};
(function (fn) {
var resolver = function (args) {
cb = last.call(args);
if (isCallable(cb)) {
return slice.call(args, 0, -1);
} else {
cb = null;
return args;
}
};
conf.memoized = function () {
return fn.apply(this, resolver(arguments));
};
forEach(fn, function (value, name) {
memoized[name] = function () {
return fn[name].apply(this, resolver(arguments));
};
});
}(conf.memoized));
}(conf.fn));
conf.on('purge', function (id) {
delete cache[id];
});
conf.on('purgeall', function () {
cache = conf.async = {};
});
};

31
lib/ext/dispose.js Normal file
View File

@@ -0,0 +1,31 @@
'use strict';
var callable = require('es5-ext/lib/Object/valid-callable')
, forEach = require('es5-ext/lib/Object/for-each')
, ext = require('../_base').ext
, isArray = Array.isArray, slice = Array.prototype.slice;
ext.dispose = function (dispose, conf, options) {
var clear;
callable(dispose);
conf.on('purge', clear = (options.async && ext.async) ? function (id) {
var value = conf.async[id];
delete conf.cache[id];
if (!isArray(value)) {
dispose.apply(null, slice.call(value, 1));
}
}: function (id) {
var value = conf.cache[id];
delete conf.cache[id];
dispose(value);
});
conf.on('purgeall', function () {
forEach(conf.cache, function (value, id, cache) {
clear(id);
});
});
};

27
lib/ext/max-age.js Normal file
View File

@@ -0,0 +1,27 @@
'use strict';
var forEach = require('es5-ext/lib/Object/for-each');
require('../_base').ext.maxAge = function (maxAge, conf) {
var cache;
maxAge = maxAge >>> 0;
if (!maxAge) {
return;
}
cache = {};
conf.on('init', function (id) {
cache[id] = setTimeout(function () { conf.clear(id); }, maxAge);
});
conf.on('purge', function (id) {
clearTimeout(cache[id]);
delete cache[id];
});
conf.on('purgeall', function (id) {
forEach(cache, function (id) {
clearTimeout(id);
});
cache = {};
});
};

55
lib/ext/max.js Normal file
View File

@@ -0,0 +1,55 @@
'use strict';
require('../_base').ext.max = function (max, conf) {
var index, base, size, add, top, clear, queue, map;
max = max >>> 0;
if (!max) {
return;
}
index = -1;
base = size = 0;
queue = {};
map = {};
conf.on('init', function (id) {
queue[++index] = id;
map[id] = index;
++size;
if (size > max) {
conf.clear(queue[base]);
}
});
conf.on('hit', function (id) {
var oldIndex = map[id];
queue[++index] = queue[oldIndex];
map[id] = index;
delete queue[oldIndex];
if (base === oldIndex) {
while (!queue.hasOwnProperty(++base)) continue;
}
});
conf.on('purge', function (id) {
var oldIndex = map[id];
delete queue[oldIndex];
--size;
if (base === oldIndex) {
if (!size) {
index = -1;
base = 0;
} else {
while (!queue.hasOwnProperty(++base)) continue;
}
}
});
conf.on('purgeall', function (id) {
index = -1;
base = size = 0;
queue = {};
map = {};
});
};

33
lib/ext/method.js Normal file
View File

@@ -0,0 +1,33 @@
'use strict';
var extend = require('es5-ext/lib/Object/extend')
, isString = require('es5-ext/lib/String/is-string')
, slice = Array.prototype.slice, create = Object.create
, defineProperty = Object.defineProperty;
require('../_base').ext.method = function (method, conf, options) {
if (isString(options.method)) {
method = { name: String(options.method),
descriptor: { configurable: true, writable: true } };
} else {
method = options.method;
method.name = String(method.name);
method.descriptor = (method.descriptor == null) ?
{ configurable: true, writable: true } : Object(method.descriptor);
}
options = create(options);
options.method = undefined;
(function (fn) {
conf.memoized = function () {
if (this && (this !== global)) {
method.descriptor.value = conf.memoize(conf.fn.bind(this), options);
defineProperty(this, method.name, method.descriptor);
return method.descriptor.value.apply(this, arguments);
}
return fn.apply(this, arguments);
};
extend(conf.memoized, fn);
}(conf.memoized));
};

View File

@@ -4,23 +4,35 @@ var partial = require('es5-ext/lib/Function/prototype/partial')
, forEach = require('es5-ext/lib/Object/for-each')
, contains = require('es5-ext/lib/String/prototype/contains')
, pad = require('es5-ext/lib/String/prototype/pad')
, memoize = require('./memoize')
, max = Math.max
, stats = exports.statistics = {};
, stats = exports.statistics = {}, ext;
memoize._profile = function () {
var id, stack = (new Error()).stack;
id = stack.split('\n')[3].replace(/\n/g, "\\n").trim();
if (contains.call(id, '/memoizee/lib')) {
id = stack.split('\n')[4].replace(/\n/g, "\\n").trim();
require('../_base').ext.profile = ext = function (conf) {
var id, stack, data;
stack = (new Error()).stack
if (!stack.split('\n').slice(3).some(function (line) {
if ((line.indexOf('/memoizee/') === -1) &&
(line.indexOf('/es5-ext/') === -1) &&
(line.indexOf('/next/lib/fs/_memoize-watcher') === -1) &&
(line.indexOf(' (native)') === -1)) {
id = line.replace(/\n/g, "\\n").trim();
return true;
}
})) {
id = 'unknown';
}
if (!stats[id]) {
stats[id] = { initial: 0, cached: 0, time: 0 };
stats[id] = { initial: 0, cached: 0 };
}
return stats[id];
data = stats[id];
conf.on('init', function (id) { ++data.initial; });
conf.on('hit', function (id) { ++data.cached; });
};
ext.force = true;
exports.log = function () {
var initial, cached, time, ordered, ipad, cpad, ppad, toPrc, tpad, atime, log;
@@ -41,7 +53,6 @@ exports.log = function () {
forEach(stats, function (data, name) {
initial += data.initial;
cached += data.cached;
time += data.time;
ordered.push([name, data]);
}, null, function (a, b) {
return (this[b].initial + this[b].cached) -
@@ -52,26 +63,18 @@ exports.log = function () {
max(String(initial).length, "Init".length));
cpad = partial.call(pad, " ", max(String(cached).length, "Cache".length));
ppad = partial.call(pad, " ", "%Cache".length);
tpad = partial.call(pad, " ", max((String(time.toFixed(3)) + "s").length,
"Avg init time".length));
log += ipad.call("Init") + " " +
cpad.call("Cache") + " " +
ppad.call("%Cache") + " " +
tpad.call("Avg init time") + " Source location\n";
ppad.call("%Cache") + " Source location\n";
log += ipad.call(initial) + " " +
cpad.call(cached) + " " +
ppad.call(toPrc(initial, cached)) + " " +
tpad.call((atime = initial ? ((time / initial) / 1000) : 0).toFixed(3) +
"s") + " (all)\n";
ppad.call(toPrc(initial, cached)) + " (all)\n";
ordered.forEach(function (data) {
var name = data[0];
data = data[1];
log += ipad.call(data.initial) + " " +
cpad.call(data.cached) + " " +
ppad.call(toPrc(data.initial, data.cached)) + " " +
tpad.call((atime =
data.initial ? ((data.time / data.initial) / 1000) : 0).toFixed(3) +
"s") + " " + name + "\n";
ppad.call(toPrc(data.initial, data.cached)) + " " + name + "\n";
});
log += "------------------------------------------------------------\n";
return log;

24
lib/ext/ref-counter.js Normal file
View File

@@ -0,0 +1,24 @@
'use strict';
require('../_base').ext.refCounter = function (ignore, conf) {
var cache, get, clear;
cache = {};
conf.on('init', function (id) { cache[id] = 1; });
conf.on('hit', function (id) { ++cache[id]; });
conf.on('purge', function (id) { delete cache[id]; });
conf.on('purgeall', function () { cache = {}; });
conf.memoized.clearRef = function () {
var id = conf.get(arguments);
if (cache.hasOwnProperty(id)) {
if (!--cache[id]) {
conf.clear(id)
return true;
}
return false;
}
return null;
};
};

38
lib/ext/resolvers.js Normal file
View File

@@ -0,0 +1,38 @@
'use strict';
var toArray = require('es5-ext/lib/Array/from')
, forEach = require('es5-ext/lib/Object/for-each')
, callable = require('es5-ext/lib/Object/valid-callable')
, slice = Array.prototype.slice
, resolve;
resolve = function (args) {
return this.map(function (r, i) {
return r ? r(args[i]) : args[i];
}).concat(slice.call(args, this.length));
};
require('../_base').ext.resolvers = function (resolvers, conf) {
var resolver;
resolver = toArray(resolvers);
resolver.forEach(function (r) { (r == null) || callable(r); });
resolver = resolve.bind(resolver);
(function (fn) {
conf.memoized = function () {
var value;
conf.memoized.args = arguments;
value = fn.apply(this, resolver(arguments));
delete conf.memoized.args;
return value;
};
forEach(fn, function (value, name) {
conf.memoized[name] = function () {
return fn[name].apply(this, resolver(arguments));
};
});
}(conf.memoized));
};

20
lib/index.js Normal file
View File

@@ -0,0 +1,20 @@
'use strict';
var regular = require('./regular')
, primitive = require('./primitive')
, apply = Function.prototype.apply;
// Order is important!
require('./ext/dispose');
require('./ext/resolvers');
require('./ext/async');
require('./ext/ref-counter');
require('./ext/method');
require('./ext/max-age');
require('./ext/max');
module.exports = function (fn/* options */) {
var options = Object(arguments[1]);
return apply.call(options.primitive ? primitive : regular, this, arguments);
};

View File

@@ -1,478 +0,0 @@
'use strict';
var global = require('es5-ext/lib/global')
, toArray = require('es5-ext/lib/Array/from')
, clearArr = require('es5-ext/lib/Array/prototype/clear')
, indexOf = require('es5-ext/lib/Array/prototype/e-index-of')
, last = require('es5-ext/lib/Array/prototype/last')
, remove = require('es5-ext/lib/Array/prototype/remove')
, curry = require('es5-ext/lib/Function/prototype/curry')
, partial = require('es5-ext/lib/Function/prototype/partial')
, callable = require('es5-ext/lib/Object/valid-callable')
, isCallable = require('es5-ext/lib/Object/is-callable')
, isCopy = require('es5-ext/lib/Object/is-copy')
, forEach = require('es5-ext/lib/Object/for-each')
, isString = require('es5-ext/lib/String/is-string')
, nextTick = require('next-tick')
, slice = Array.prototype.slice, now = Date.now
, apply = Function.prototype.apply
, create = Object.create, defineProperty = Object.defineProperty
, resolve, memoize;
resolve = function (args) {
return this.map(function (r, i) {
return r ? r(args[i]) : args[i];
}).concat(slice.call(args, this.length));
};
// Results are saved internally within array matrix:
// [0] -> Result of calling function with no arguments
// [1] -> Matrix that keeps results when function is called with one argument
// [1][0] -> Array of arguments with which
// function have been called
// [1][1] -> Array of results that matches [1][0] array
// [2] -> Matrix that keeps results when function is called with two arguments
// [2][0] -> Array of first (of two) arguments with which
// function have been called
// [2][1] -> Matrixes that keeps results for two arguments function calls
// Each matrix matches first argument found in [2][0]
// [2][1][x][0] -> Array of second arguments with which
// function have been called.
// [2][1][x][1] -> Array of results that matches [2][1][x][0]
// arguments array
// ...and so on
memoize = module.exports = function (fn/*, options*/) {
var mfn, options, length, resolver, method, cache, find, save, clear
, value, primitive, serialize, profile, refCounter, async, initAsync
, maxAge, max, queue, dispose, orgDispose, purgePrimitive;
callable(fn);
if (fn.memoized) {
// Prevent memoization of already memoized function
return fn;
}
options = Object(arguments[1]);
async = Boolean(options.async);
if (isNaN(options.length)) {
length = fn.length;
if (async) {
--length;
}
} else if (options.length === false) {
length = false;
} else {
length = Number(options.length);
}
if (options.resolvers) {
resolver = toArray(options.resolvers);
resolver.forEach(function (r) {
(r == null) || callable(r);
});
resolver = resolve.bind(resolver);
}
if (options.method) {
if (isString(options.method)) {
method = { name: String(options.method),
descriptor: { configurable: true, writable: true } };
} else {
method = options.method;
method.name = String(method.name);
method.descriptor = (method.descriptor == null) ?
{ configurable: true, writable: true } : Object(method.descriptor);
}
options = create(options);
options.method = undefined;
}
primitive = Boolean(options.primitive);
refCounter = Boolean(options.refCounter);
maxAge = options.maxAge >>> 0;
max = options.max >>> 0;
if (options.dispose != null) {
dispose = orgDispose = callable(options.dispose);
if (async) {
dispose = function (value) {
if (value.value) {
apply.call(orgDispose, null, slice.call(value.value, 1));
}
};
} else if (refCounter) {
dispose = function (value) { orgDispose(value.value); };
}
}
cache = primitive ? {} : [];
if (memoize._profile) {
profile = memoize._profile();
}
if (max) {
queue = [];
}
find = function (length, args) {
var index = 0, rset = cache, i;
if (length === 0) {
value = rset[length];
return rset.hasOwnProperty(length);
} else if ((rset = rset[length])) {
while (index < (length - 1)) {
i = indexOf.call(rset[0], args[index]);
if (i === -1) {
return false;
}
rset = rset[1][i];
++index;
}
i = indexOf.call(rset[0], args[index]);
if (i === -1) {
return false;
}
value = rset[1][i];
return true;
}
return false;
};
save = function (length, args, value) {
var index = 0, rset = cache, i;
if (length === 0) {
rset[length] = value;
} else {
if (!rset[length]) {
rset[length] = [[], []];
}
rset = rset[length];
while (index < (length - 1)) {
i = indexOf.call(rset[0], args[index]);
if (i === -1) {
i = rset[0].push(args[index]) - 1;
rset[1].push([[], []]);
}
rset = rset[1][i];
++index;
}
i = indexOf.call(rset[0], args[index]);
if (i === -1) {
i = rset[0].push(args[index]) - 1;
}
rset[1][i] = value;
}
};
clear = function (length, args) {
var index = 0, rset = cache, i, path = [], value;
if (length === 0) {
if (dispose && rset.hasOwnProperty("0")) {
value = rset[0];
delete rset[0];
dispose(value);
} else {
delete rset[length];
}
} else if ((rset = rset[length])) {
while (index < (length - 1)) {
i = indexOf.call(rset[0], args[index]);
if (i === -1) {
return;
}
path.push(rset, i);
rset = rset[1][i];
++index;
}
i = indexOf.call(rset[0], args[index]);
if (i === -1) {
return;
}
rset[0].splice(i, 1);
if (dispose) {
dispose(rset[1][i]);
}
rset[1].splice(i, 1);
while (!rset[0].length && path.length) {
i = path.pop();
rset = path.pop();
rset[0].splice(i, 1);
rset[1].splice(i, 1);
}
}
};
if (primitive && (length !== 1)) {
serialize = function (args, length) {
var id = '', i;
if (length) {
id += args[i = 0];
while (--length) {
id += '\u0001' + args[++i];
}
} else {
id = '\u0002';
}
return id;
};
}
purgePrimitive = function (id) {
var value;
if (cache.hasOwnProperty(id)) {
value = cache[id];
delete cache[id];
if (dispose) {
dispose(value);
}
}
};
initAsync = function (args, cb, maxAgeData) {
var waiting = cb ? [cb] : []
, value = { waiting: waiting }, time;
args.push(function (err) {
var args2 = arguments;
if (profile) {
profile.time += (now() - time);
}
if (!err) {
value.value = arguments;
if (max) {
if (primitive) {
queue.push(maxAgeData);
if (queue.length > max) {
purgePrimitive(queue.shift());
}
} else {
queue.push([maxAgeData, args]);
if (queue.length > max) {
clear.apply(null, queue.shift());
}
}
}
if (maxAge) {
setTimeout(primitive ? partial.call(purgePrimitive, maxAgeData) :
partial.call(clear, maxAgeData, args.slice(0, -1)), maxAge);
}
} else {
mfn.clear.apply(this, args.slice(0, -1));
}
waiting.forEach(function (cb) {
cb.apply(null, args2);
});
delete value.waiting;
});
if (profile) {
time = now();
}
return value;
};
mfn = function () {
var args, alength, id, cb, time;
if (method && this && (this !== global)) {
method.descriptor.value = memoize(fn.bind(this), options);
defineProperty(this, method.name, method.descriptor);
return method.descriptor.value.apply(this, arguments);
}
args = arguments;
if (async) {
cb = last.call(args);
if (isCallable(cb)) {
args = slice.call(args, 0, -1);
} else {
cb = null;
}
}
if (resolver) {
args = resolver(args);
}
alength = (length === false) ? args.length : length;
if (primitive) {
id = (length === 1) ? args[0] : serialize(args, alength);
if (cache.hasOwnProperty(id)) {
profile && ++profile.cached;
value = cache[id];
} else {
profile && ++profile.initial;
mfn.args = arguments;
mfn.preventCache = false;
if (async) {
args = toArray(args);
value = initAsync(args, cb, id);
value.sync = apply.call(fn, this, args);
} else {
if (profile) {
time = now();
}
value = apply.call(fn, this, args);
if (profile) {
profile.time += (now() - time);
}
}
if (!mfn.preventCache) {
if (refCounter) {
if (async) {
value.count = cb ? 1 : 0;
} else {
value = { value: value, count: 1 };
}
}
cache[id] = value;
if (max && !async) {
queue.push(id);
if (queue.length > max) {
purgePrimitive(queue.shift());
}
}
if (maxAge && !async) {
setTimeout(partial.call(purgePrimitive, id), maxAge);
}
}
delete mfn.args;
return async ? value.sync : (refCounter ? value.value : value);
}
} else if (find(alength, args)) {
profile && ++profile.cached;
} else {
profile && ++profile.initial;
mfn.args = arguments;
mfn.preventCache = false;
if (async) {
args = toArray(args);
value = initAsync(args, cb, alength);
value.sync = apply.call(fn, this, args);
} else {
if (profile) {
time = now();
}
value = apply.call(fn, this, args);
if (profile) {
profile.time += (now() - time);
}
}
if (!mfn.preventCache) {
if (refCounter) {
if (async) {
value.count = cb ? 1 : 0;
} else {
value = { value: value, count: 1 };
}
}
save(alength, args, value);
if (max && !async) {
queue.push([alength, args]);
if (queue.length > max) {
clear.apply(null, queue.shift());
}
}
if (maxAge && !async) {
setTimeout(partial.call(clear, alength, args), maxAge);
}
}
delete mfn.args;
return async ? value.sync : (refCounter ? value.value : value);
}
if (async) {
if (cb) {
if (refCounter) {
++value.count;
}
if (value.value) {
nextTick(function () {
cb.apply(null, this);
}.bind(value.value));
} else {
value.waiting.push(cb);
}
}
return value.sync;
} else {
if (refCounter) {
++value.count;
value = value.value;
}
return value;
}
};
mfn.memoized = true;
mfn.clear = function () {
var args, alength, id;
args = resolver ? resolver(arguments) : arguments;
alength = (length === false) ? args.length : length;
if (primitive) {
id = (length === 1) ? args[0] : serialize(args, alength);
if (max) {
remove.call(queue, id);
}
purgePrimitive(id);
} else {
if (max) {
if (find.call(queue, function (data, index) {
if (isCopy(data, [alength, args], 2)) {
id = index;
return true;
}
})) {
queue.splice(id, 1);
}
}
clear(alength, args);
}
};
mfn.clearAll = function () {
if (dispose) {
if (primitive) {
forEach(cache, curry.call(dispose, 1));
} else {
cache.forEach(function self(value, index) {
if (!index) {
dispose(value);
return;
}
--index;
value[1].forEach(function (value) { self(value, index); });
});
}
}
if (max) {
clearArr.call(queue);
}
cache = primitive ? {} : [];
};
if (refCounter) {
mfn.clearRef = function () {
var args, alength, id;
args = resolver ? resolver(arguments) : arguments;
alength = (length === false) ? args.length : length;
if (primitive) {
id = (length === 1) ? args[0] : serialize(args, alength);
if (!cache.hasOwnProperty(id)) {
return null;
}
value = cache[id];
} else if (!find(alength, args)) {
return null;
}
if (!--value.count) {
mfn.clear.apply(null, arguments);
return true;
}
return false;
};
}
return mfn;
};

81
lib/primitive.js Normal file
View File

@@ -0,0 +1,81 @@
'use strict';
var hasListeners = require('event-emitter/lib/has-listeners')
, getId0 = function () { return ''; }
, getId1 = function (args) { return args[0]; }
, apply = Function.prototype.apply, call = Function.prototype.call;
module.exports = require('./_base')(function (conf, length) {
var get, cache = conf.cache = {}, fn
, hitListeners, initListeners, purgeListeners;
if (length === 1) {
get = conf.get = getId1;
} else if (length === false) {
get = conf.get = function (args, length) {
var id = '', i;
if (length) {
id += args[i = 0];
while (--length) {
id += '\u0001' + args[++i];
}
} else {
id = '\u0002';
}
return id;
};
} else if (length) {
get = conf.get = function(args) {
var id = String(args[0]), i = 0, l = length;
while (--l) { id += '\u0001' + args[++i]; }
return id;
};
} else {
get = conf.get = getId0;
}
conf.memoized = (length === 1) ? function (id) {
var value;
if (cache.hasOwnProperty(id)) {
hitListeners && conf.emit('hit', id);
return cache[id];
} else {
if (arguments.length === 1) {
value = call.call(fn, this, id);
} else {
value = apply.call(fn, this, arguments);
}
cache[id] = value;
initListeners && conf.emit('init', id);
return value;
}
} : function () {
var id = get(arguments), value;
if (cache.hasOwnProperty(id)) {
hitListeners && conf.emit('hit', id);
return cache[id];
} else {
value = apply.call(conf.fn, this, arguments);
cache[id] = value;
initListeners && conf.emit('init', id);
return value;
}
};
conf.clear = function (id) {
if (cache.hasOwnProperty(id)) {
purgeListeners && conf.emit('purge', id);
delete cache[id];
}
};
conf.clearAll = function () { cache = conf.cache = {}; };
conf.once('ready', function () {
fn = conf.fn;
hitListeners = hasListeners(conf, 'hit');
initListeners = hasListeners(conf, 'init');
purgeListeners = hasListeners(conf, 'purge');
});
});

240
lib/regular.js Normal file
View File

@@ -0,0 +1,240 @@
'use strict';
var indexOf = require('es5-ext/lib/Array/prototype/e-index-of')
, hasListeners = require('event-emitter/lib/has-listeners')
, apply = Function.prototype.apply
, getId0 = function () { return ''; };
// Results are saved internally within array matrix:
// [0] -> Result of calling function with no arguments
// [1] -> Matrix that keeps results when function is called with one argument
// [1][0] -> Array of arguments with which
// function have been called
// [1][1] -> Array of results that matches [1][0] array
// [2] -> Matrix that keeps results when function is called with two arguments
// [2][0] -> Array of first (of two) arguments with which
// function have been called
// [2][1] -> Matrixes that keeps results for two arguments function calls
// Each matrix matches first argument found in [2][0]
// [2][1][x][0] -> Array of second arguments with which
// function have been called.
// [2][1][x][1] -> Array of results that matches [2][1][x][0]
// arguments array
// ...and so on
module.exports = require('./_base')(function (conf, length) {
var map, map1, map2, get, set, clear, count, fn
, hitListeners, initListeners, purgeListeners
, cache = conf.cache = {};
if (length === 0) {
get = set = clear = conf.get = getId0;
conf.clearAll = function () { cache = conf.cache = {}; };
} else {
count = 0;
if (length === 1) {
map1 = []; map2 = [];
get = conf.get = function (args) {
return map2[indexOf.call(map1, args[0])];
};
set = function (args) {
map1.push(args[0]);
map2.push(++count);
return count;
};
clear = function (args) {
var index = indexOf.call(map1, args[0]), id;
if (index !== -1) {
id = map2[index];
map1.splice(index, 1);
map2.splice(index, 1);
return id;
}
};
conf.clearAll = function () {
map1 = [];
map2 = [];
cache = conf.cache = {};
};
} else if (length === false) {
map = [];
get = conf.get = function (args) {
var index = 0, set = map, i, length = args.length;
if (length === 0) {
return set[length];
} else if ((set = set[length])) {
while (index < (length - 1)) {
i = indexOf.call(set[0], args[index]);
if (i === -1) {
return;
}
set = set[1][i];
++index;
}
i = indexOf.call(set[0], args[index]);
if (i === -1) {
return;
}
return set[1][i];
}
return;
};
set = function (args) {
var index = 0, set = map, i, length = args.length;
if (length === 0) {
set[length] = ++count;
} else {
if (!set[length]) {
set[length] = [[], []];
}
set = set[length];
while (index < (length - 1)) {
i = indexOf.call(set[0], args[index]);
if (i === -1) {
i = set[0].push(args[index]) - 1;
set[1].push([[], []]);
}
set = set[1][i];
++index;
}
i = indexOf.call(set[0], args[index]);
if (i === -1) {
i = set[0].push(args[index]) - 1;
}
set[1][i] = ++count;
}
return count;
};
clear = function (args) {
var index = 0, set = map, i, length = args.length, path =[], id;
if (length === 0) {
id = set[length];
delete set[length];
return id;
} else if ((set = set[length])) {
while (index < (length - 1)) {
i = indexOf.call(set[0], args[index]);
if (i === -1) {
return;
}
path.push(set, i);
set = set[1][i];
++index;
}
i = indexOf.call(set[0], args[index]);
if (i === -1) {
return;
}
id = set[1][i];
set[0].splice(i, 1);
set[1].splice(i, 1);
while (!set[0].length && path.length) {
i = path.pop();
set = path.pop();
set[0].splice(i, 1);
set[1].splice(i, 1);
}
return id;
}
};
conf.clearAll = function () {
map = [];
cache = conf.cache = {};
};
} else {
map = [[], []];
get = conf.get = function (args) {
var index = 0, set = map, i;
while (index < (length - 1)) {
i = indexOf.call(set[0], args[index]);
if (i === -1) {
return null;
}
set = set[1][i];
++index;
}
i = indexOf.call(set[0], args[index]);
if (i === -1) {
return null;
}
return set[1][i];
};
set = function (args) {
var index = 0, set = map, i;
while (index < (length - 1)) {
i = indexOf.call(set[0], args[index]);
if (i === -1) {
i = set[0].push(args[index]) - 1;
set[1].push([[], []]);
}
set = set[1][i];
++index;
}
i = indexOf.call(set[0], args[index]);
if (i === -1) {
i = set[0].push(args[index]) - 1;
}
set[1][i] = ++count;
return count;
};
clear = function (args) {
var index = 0, set = map, i, path =[], id;
while (index < (length - 1)) {
i = indexOf.call(set[0], args[index]);
if (i === -1) {
return;
}
path.push(set, i);
set = set[1][i];
++index;
}
i = indexOf.call(set[0], args[index]);
if (i === -1) {
return;
}
id = set[1][i];
set[0].splice(i, 1);
set[1].splice(i, 1);
while (!set[0].length && path.length) {
i = path.pop();
set = path.pop();
set[0].splice(i, 1);
set[1].splice(i, 1);
}
return id;
};
conf.clearAll = function () {
map = [[], []];
cache = conf.cache = {};
};
}
}
conf.memoized = function () {
var id = get(arguments), value;
if (cache.hasOwnProperty(id)) {
hitListeners && conf.emit('hit', id);
return cache[id];
} else {
value = apply.call(fn, this, arguments);
id = set(arguments);
cache[id] = value;
initListeners && conf.emit('init', id);
return value;
}
};
conf.clear = function (id) {
if (cache.hasOwnProperty(id)) {
clear(id);
purgeListeners && conf.emit('purge', id);
delete cache[id];
}
};
conf.once('ready', function () {
fn = conf.fn;
hitListeners = hasListeners(conf, 'hit');
initListeners = hasListeners(conf, 'init');
purgeListeners = hasListeners(conf, 'purge');
});
});

View File

@@ -2,7 +2,7 @@
"name": "memoizee",
"version": "0.1.1",
"description": "Complete memoize/cache solution. Works with any type and length of function arguments",
"main": "lib/memoize",
"main": "lib",
"scripts": {
"test": "node node_modules/tad/bin/tad lib"
},
@@ -35,6 +35,7 @@
},
"dependencies": {
"es5-ext": "0.9.x",
"event-emitter": "~0.2.1",
"next-tick": "0.1.x"
},
"devDependencies": {

15
test/_base.js Normal file
View File

@@ -0,0 +1,15 @@
'use strict';
module.exports = function (t, a) {
var mfn, m = t(function (conf, length) {
conf.get = function () { return 'test'; };
conf.memoized = function (x, y, z) { return conf.fn(z, y, x); };
conf.clear = function () {};
conf.clearAll = function () {};
});
a(typeof m, 'function', "Returns");
mfn = m(function (x, y, z) { return [x, y, z] });
a(typeof mfn, 'function', "Generates");
a.deep(mfn(3, 7, 11), [11, 7, 3], "Works");
};

351
test/ext/async.js Normal file
View File

@@ -0,0 +1,351 @@
'use strict';
var memoize = require('../../lib')
, nextTick = require('next-tick');
module.exports = function (a) {
return {
"Regular": {
"Success": function (a, d) {
var mfn, fn, u = {}, i = 0, invoked = 0;
fn = function (x, y, cb) {
nextTick(function () {
++i;
cb(null, x + y);
});
return u;
};
mfn = memoize(fn, { async: true });
a(mfn(3, 7, function (err, res) {
++invoked;
a.deep([err, res], [null, 10], "Result #1");
}), u, "Initial");
a(mfn(3, 7, function (err, res) {
++invoked;
a.deep([err, res], [null, 10], "Result #2");
}), u, "Initial #2");
a(mfn(5, 8, function (err, res) {
++invoked;
a.deep([err, res], [null, 13], "Result B #1");
}), u, "Initial #2");
a(mfn(3, 7, function (err, res) {
++invoked;
a.deep([err, res], [null, 10], "Result #3");
}), u, "Initial #2");
a(mfn(5, 8, function (err, res) {
++invoked;
a.deep([err, res], [null, 13], "Result B #2");
}), u, "Initial #3");
nextTick(function () {
a(i, 2, "Init Called");
a(invoked, 5, "Cb Called");
a(mfn(3, 7, function (err, res) {
++invoked;
a.deep([err, res], [null, 10], "Again: Result");
}), u, "Again: Initial");
a(mfn(5, 8, function (err, res) {
++invoked;
a.deep([err, res], [null, 13], "Again B: Result");
}), u, "Again B: Initial");
nextTick(function () {
a(i, 2, "Init Called #2");
a(invoked, 7, "Cb Called #2");
mfn.clear(3, 7);
a(mfn(3, 7, function (err, res) {
++invoked;
a.deep([err, res], [null, 10], "Again: Result");
}), u, "Again: Initial");
a(mfn(5, 8, function (err, res) {
++invoked;
a.deep([err, res], [null, 13], "Again B: Result");
}), u, "Again B: Initial");
nextTick(function () {
a(i, 3, "Init After clear");
a(invoked, 9, "Cb After clear");
d();
});
});
});
},
"Reference counter": function (a, d) {
var mfn, fn, u = {}, i = 0;
fn = function (x, y, cb) {
nextTick(function () {
++i;
cb(null, x + y);
});
return u;
};
mfn = memoize(fn, { async: true, refCounter: true });
a(mfn.clearRef(3, 7), null, "Clear ref before");
a(mfn(3, 7, function (err, res) {
a.deep([err, res], [null, 10], "Result #1");
}), u, "Initial");
a(mfn(3, 7, function (err, res) {
a.deep([err, res], [null, 10], "Result #2");
}), u, "Initial #2");
a(mfn(5, 8, function (err, res) {
a.deep([err, res], [null, 13], "Result B #1");
}), u, "Initial #2");
a(mfn(3, 7, function (err, res) {
a.deep([err, res], [null, 10], "Result #3");
}), u, "Initial #2");
a(mfn(5, 8, function (err, res) {
a.deep([err, res], [null, 13], "Result B #2");
}), u, "Initial #3");
nextTick(function () {
a(i, 2, "Called #2");
a(mfn(3, 7, function (err, res) {
a.deep([err, res], [null, 10], "Again: Result");
}), u, "Again: Initial");
a(mfn(5, 8, function (err, res) {
a.deep([err, res], [null, 13], "Again B: Result");
}), u, "Again B: Initial");
nextTick(function () {
a(i, 2, "Again Called #2");
a(mfn.clearRef(3, 7), false, "Clear ref #1");
a(mfn.clearRef(3, 7), false, "Clear ref #2");
a(mfn.clearRef(3, 7), false, "Clear ref #3");
a(mfn.clearRef(3, 7), true, "Clear ref Final");
a(mfn(3, 7, function (err, res) {
a.deep([err, res], [null, 10], "Again: Result");
}), u, "Again: Initial");
a(mfn(5, 8, function (err, res) {
a.deep([err, res], [null, 13], "Again B: Result");
}), u, "Again B: Initial");
nextTick(function () {
a(i, 3, "Call After clear");
d();
});
});
});
},
"Error": function (a, d) {
var mfn, fn, u = {}, i = 0, e = new Error("Test");
fn = function (x, y, cb) {
nextTick(function () {
++i;
cb(e);
});
return u;
};
mfn = memoize(fn, { async: true });
a(mfn(3, 7, function (err, res) {
a.deep([err, res], [e, undefined], "Result #1");
}), u, "Initial");
a(mfn(3, 7, function (err, res) {
a.deep([err, res], [e, undefined], "Result #2");
}), u, "Initial #2");
a(mfn(5, 8, function (err, res) {
a.deep([err, res], [e, undefined], "Result B #1");
}), u, "Initial #2");
a(mfn(3, 7, function (err, res) {
a.deep([err, res], [e, undefined], "Result #3");
}), u, "Initial #2");
a(mfn(5, 8, function (err, res) {
a.deep([err, res], [e, undefined], "Result B #2");
}), u, "Initial #3");
nextTick(function () {
a(i, 2, "Called #2");
a(mfn(3, 7, function (err, res) {
a.deep([err, res], [e, undefined], "Again: Result");
}), u, "Again: Initial");
a(mfn(5, 8, function (err, res) {
a.deep([err, res], [e, undefined], "Again B: Result");
}), u, "Again B: Initial");
nextTick(function () {
a(i, 4, "Again Called #2");
d();
});
});
}
},
"Primitive": {
"Success": function (a, d) {
var mfn, fn, u = {}, i = 0;
fn = function (x, y, cb) {
nextTick(function () {
++i;
cb(null, x + y);
});
return u;
};
mfn = memoize(fn, { async: true, primitive: true });
a(mfn(3, 7, function (err, res) {
a.deep([err, res], [null, 10], "Result #1");
}), u, "Initial");
a(mfn(3, 7, function (err, res) {
a.deep([err, res], [null, 10], "Result #2");
}), u, "Initial #2");
a(mfn(5, 8, function (err, res) {
a.deep([err, res], [null, 13], "Result B #1");
}), u, "Initial #2");
a(mfn(3, 7, function (err, res) {
a.deep([err, res], [null, 10], "Result #3");
}), u, "Initial #2");
a(mfn(5, 8, function (err, res) {
a.deep([err, res], [null, 13], "Result B #2");
}), u, "Initial #3");
nextTick(function () {
a(i, 2, "Called #2");
a(mfn(3, 7, function (err, res) {
a.deep([err, res], [null, 10], "Again: Result");
}), u, "Again: Initial");
a(mfn(5, 8, function (err, res) {
a.deep([err, res], [null, 13], "Again B: Result");
}), u, "Again B: Initial");
nextTick(function () {
a(i, 2, "Again Called #2");
mfn.clear(3, 7);
a(mfn(3, 7, function (err, res) {
a.deep([err, res], [null, 10], "Again: Result");
}), u, "Again: Initial");
a(mfn(5, 8, function (err, res) {
a.deep([err, res], [null, 13], "Again B: Result");
}), u, "Again B: Initial");
nextTick(function () {
a(i, 3, "Call After clear");
d();
});
});
});
},
"Reference counter": function (a, d) {
var mfn, fn, u = {}, i = 0;
fn = function (x, y, cb) {
nextTick(function () {
++i;
cb(null, x + y);
});
return u;
};
mfn = memoize(fn, { async: true, primitive: true, refCounter: true });
a(mfn.clearRef(3, 7), null, "Clear ref before");
a(mfn(3, 7, function (err, res) {
a.deep([err, res], [null, 10], "Result #1");
}), u, "Initial");
a(mfn(3, 7, function (err, res) {
a.deep([err, res], [null, 10], "Result #2");
}), u, "Initial #2");
a(mfn(5, 8, function (err, res) {
a.deep([err, res], [null, 13], "Result B #1");
}), u, "Initial #2");
a(mfn(3, 7, function (err, res) {
a.deep([err, res], [null, 10], "Result #3");
}), u, "Initial #2");
a(mfn(5, 8, function (err, res) {
a.deep([err, res], [null, 13], "Result B #2");
}), u, "Initial #3");
nextTick(function () {
a(i, 2, "Called #2");
a(mfn(3, 7, function (err, res) {
a.deep([err, res], [null, 10], "Again: Result");
}), u, "Again: Initial");
a(mfn(5, 8, function (err, res) {
a.deep([err, res], [null, 13], "Again B: Result");
}), u, "Again B: Initial");
nextTick(function () {
a(i, 2, "Again Called #2");
a(mfn.clearRef(3, 7), false, "Clear ref #1");
a(mfn.clearRef(3, 7), false, "Clear ref #2");
a(mfn.clearRef(3, 7), false, "Clear ref #3");
a(mfn.clearRef(3, 7), true, "Clear ref Final");
a(mfn(3, 7, function (err, res) {
a.deep([err, res], [null, 10], "Again: Result");
}), u, "Again: Initial");
a(mfn(5, 8, function (err, res) {
a.deep([err, res], [null, 13], "Again B: Result");
}), u, "Again B: Initial");
nextTick(function () {
a(i, 3, "Call After clear");
d();
});
});
});
},
"Error": function (a, d) {
var mfn, fn, u = {}, i = 0, e = new Error("Test");
fn = function (x, y, cb) {
nextTick(function () {
++i;
cb(e);
});
return u;
};
mfn = memoize(fn, { async: true, primitive: true });
a(mfn(3, 7, function (err, res) {
a.deep([err, res], [e, undefined], "Result #1");
}), u, "Initial");
a(mfn(3, 7, function (err, res) {
a.deep([err, res], [e, undefined], "Result #2");
}), u, "Initial #2");
a(mfn(5, 8, function (err, res) {
a.deep([err, res], [e, undefined], "Result B #1");
}), u, "Initial #2");
a(mfn(3, 7, function (err, res) {
a.deep([err, res], [e, undefined], "Result #3");
}), u, "Initial #2");
a(mfn(5, 8, function (err, res) {
a.deep([err, res], [e, undefined], "Result B #2");
}), u, "Initial #3");
nextTick(function () {
a(i, 2, "Called #2");
a(mfn(3, 7, function (err, res) {
a.deep([err, res], [e, undefined], "Again: Result");
}), u, "Again: Initial");
a(mfn(5, 8, function (err, res) {
a.deep([err, res], [e, undefined], "Again B: Result");
}), u, "Again B: Initial");
nextTick(function () {
a(i, 4, "Again Called #2");
d();
});
});
}
}
};
};

191
test/ext/dispose.js Normal file
View File

@@ -0,0 +1,191 @@
'use strict';
var memoize = require('../../lib')
, nextTick = require('next-tick');
module.exports = function (a) {
return {
"Regular": {
"Sync": function (a) {
var mfn, fn, i = 0, value = [], x, invoked;
fn = function (x, y) {
++i;
return x + y;
};
mfn = memoize(fn, { dispose: function (val) { value.push(val); } });
mfn(3, 7);
mfn(5, 8);
mfn(12, 4);
a.deep(value, [], "Pre");
mfn.clear(5, 8);
a.deep(value, [13], "#1");
value = [];
mfn.clear(12, 4);
a.deep(value, [16], "#2");
value = [];
mfn(77, 11);
mfn.clearAll();
a.deep(value, [10, 88], "Clear all");
x = {};
invoked = false;
mfn = memoize(function () { return x; },
{ dispose: function (val) { invoked = val; } });
mfn.clear();
a(invoked, false, "No args: Post invalid clear");
mfn();
a(invoked, false, "No args: Post cache");
mfn.clear();
a(invoked, x, "No args: Pre clear");
},
"Ref counter": function (a) {
var mfn, fn, i = 0, value = [];
fn = function (x, y) {
++i;
return x + y;
};
mfn = memoize(fn, { refCounter: true,
dispose: function (val) { value.push(val); } });
mfn(3, 7);
mfn(5, 8);
mfn(12, 4);
a.deep(value, [], "Pre");
mfn(5, 8);
mfn.clearRef(5, 8);
a.deep(value, [], "Pre");
mfn.clearRef(5, 8);
a.deep(value, [13], "#1");
value = [];
mfn.clearRef(12, 4);
a.deep(value, [16], "#2");
value = [];
mfn(77, 11);
mfn.clearAll();
a.deep(value, [10, 88], "Clear all");
},
"Async": function (a, d) {
var mfn, fn, u = {}, i = 0, value = [];
fn = function (x, y, cb) {
nextTick(function () {
++i;
cb(null, x + y);
});
return u;
};
mfn = memoize(fn, { async: true,
dispose: function (val) { value.push(val); } });
mfn(3, 7, function () {
mfn(5, 8, function () {
mfn(12, 4, function () {
a.deep(value, [], "Pre");
mfn.clear(5, 8);
a.deep(value, [13], "#1");
value = [];
mfn.clear(12, 4);
a.deep(value, [16], "#2");
value = [];
mfn(77, 11, function () {
mfn.clearAll();
a.deep(value, [10, 88], "Clear all");
d();
});
});
});
});
}
},
"Primitive": {
"Sync": function (a) {
var mfn, fn, i = 0, value = [];
fn = function (x, y) {
++i;
return x + y;
};
mfn = memoize(fn, { dispose: function (val) { value.push(val); } });
mfn(3, 7);
mfn(5, 8);
mfn(12, 4);
a.deep(value, [], "Pre");
mfn.clear(5, 8);
a.deep(value, [13], "#1");
value = [];
mfn.clear(12, 4);
a.deep(value, [16], "#2");
value = [];
mfn(77, 11);
mfn.clearAll();
a.deep(value, [10, 88], "Clear all");
},
"Ref counter": function (a) {
var mfn, fn, i = 0, value = [];
fn = function (x, y) {
++i;
return x + y;
};
mfn = memoize(fn, { refCounter: true,
dispose: function (val) { value.push(val); } });
mfn(3, 7);
mfn(5, 8);
mfn(12, 4);
a.deep(value, [], "Pre");
mfn(5, 8);
mfn.clearRef(5, 8);
a.deep(value, [], "Pre");
mfn.clearRef(5, 8);
a.deep(value, [13], "#1");
value = [];
mfn.clearRef(12, 4);
a.deep(value, [16], "#2");
value = [];
mfn(77, 11);
mfn.clearAll();
a.deep(value, [10, 88], "Clear all");
},
"Async": function (a, d) {
var mfn, fn, u = {}, i = 0, value = [];
fn = function (x, y, cb) {
nextTick(function () {
++i;
cb(null, x + y);
});
return u;
};
mfn = memoize(fn, { async: true,
dispose: function (val) { value.push(val); } });
mfn(3, 7, function () {
mfn(5, 8, function () {
mfn(12, 4, function () {
a.deep(value, [], "Pre");
mfn.clear(5, 8);
a.deep(value, [13], "#1");
value = [];
mfn.clear(12, 4);
a.deep(value, [16], "#2");
value = [];
mfn(77, 11, function () {
mfn.clearAll();
a.deep(value, [10, 88], "Clear all");
d();
});
});
});
});
}
}
};
};

201
test/ext/max-age.js Normal file
View File

@@ -0,0 +1,201 @@
'use strict';
var memoize = require('../../lib')
, nextTick = require('next-tick');
module.exports = function (a) {
return {
"Regular": {
"Sync": function (a, d) {
var mfn, fn, i = 0;
fn = function (x, y) {
++i;
return x + y;
};
mfn = memoize(fn, { maxAge: 100 });
a(mfn(3, 7), 10, "Result #1");
a(i, 1, "Called #1");
a(mfn(3, 7), 10, "Result #2");
a(i, 1, "Called #2");
a(mfn(5, 8), 13, "Result B #1");
a(i, 2, "Called B #1");
a(mfn(3, 7), 10, "Result #3");
a(i, 2, "Called #3");
a(mfn(5, 8), 13, "Result B #2");
a(i, 2, "Called B #2");
setTimeout(function () {
a(mfn(3, 7), 10, "Result: Wait");
a(i, 2, "Called: Wait");
a(mfn(5, 8), 13, "Result: Wait B");
a(i, 2, "Called: Wait B");
setTimeout(function () {
a(mfn(3, 7), 10, "Result: Wait After");
a(i, 3, "Called: Wait After");
a(mfn(5, 8), 13, "Result: Wait After B");
a(i, 4, "Called: Wait After B");
a(mfn(3, 7), 10, "Result: Wait After #2");
a(i, 4, "Called: Wait After #2");
a(mfn(5, 8), 13, "Result: Wait After B #2");
a(i, 4, "Called: Wait After B #2");
d();
}, 100);
}, 20);
},
"Async": function (a, d) {
var mfn, fn, u = {}, i = 0;
fn = function (x, y, cb) {
nextTick(function () {
++i;
cb(null, x + y);
});
return u;
};
mfn = memoize(fn, { async: true, maxAge: 100 });
a(mfn(3, 7, function (err, res) {
a.deep([err, res], [null, 10], "Result #1");
}), u, "Initial");
a(mfn(3, 7, function (err, res) {
a.deep([err, res], [null, 10], "Result #2");
}), u, "Initial #2");
a(mfn(5, 8, function (err, res) {
a.deep([err, res], [null, 13], "Result B #1");
}), u, "Initial #2");
a(mfn(3, 7, function (err, res) {
a.deep([err, res], [null, 10], "Result #3");
}), u, "Initial #2");
a(mfn(5, 8, function (err, res) {
a.deep([err, res], [null, 13], "Result B #2");
}), u, "Initial #3");
setTimeout(function () {
a(i, 2, "Called #2");
a(mfn(3, 7, function (err, res) {
a.deep([err, res], [null, 10], "Again: Result");
}), u, "Again: Initial");
a(mfn(5, 8, function (err, res) {
a.deep([err, res], [null, 13], "Again B: Result");
}), u, "Again B: Initial");
setTimeout(function () {
a(i, 2, "Again Called #2");
a(mfn(3, 7, function (err, res) {
a.deep([err, res], [null, 10], "Again: Result");
}), u, "Again: Initial");
a(mfn(5, 8, function (err, res) {
a.deep([err, res], [null, 13], "Again B: Result");
}), u, "Again B: Initial");
nextTick(function () {
a(i, 4, "Call After clear");
d();
});
}, 100);
}, 20);
}
},
"Primitive": {
"Sync": function (a, d) {
var mfn, fn, i = 0;
fn = function (x, y) {
++i;
return x + y;
};
mfn = memoize(fn, { primitive: true, maxAge: 100 });
a(mfn(3, 7), 10, "Result #1");
a(i, 1, "Called #1");
a(mfn(3, 7), 10, "Result #2");
a(i, 1, "Called #2");
a(mfn(5, 8), 13, "Result B #1");
a(i, 2, "Called B #1");
a(mfn(3, 7), 10, "Result #3");
a(i, 2, "Called #3");
a(mfn(5, 8), 13, "Result B #2");
a(i, 2, "Called B #2");
setTimeout(function () {
a(mfn(3, 7), 10, "Result: Wait");
a(i, 2, "Called: Wait");
a(mfn(5, 8), 13, "Result: Wait B");
a(i, 2, "Called: Wait B");
setTimeout(function () {
a(mfn(3, 7), 10, "Result: Wait After");
a(i, 3, "Called: Wait After");
a(mfn(5, 8), 13, "Result: Wait After B");
a(i, 4, "Called: Wait After B");
a(mfn(3, 7), 10, "Result: Wait After #2");
a(i, 4, "Called: Wait After #2");
a(mfn(5, 8), 13, "Result: Wait After B #2");
a(i, 4, "Called: Wait After B #2");
d();
}, 100);
}, 20);
},
"Async": function (a, d) {
var mfn, fn, u = {}, i = 0;
fn = function (x, y, cb) {
nextTick(function () {
++i;
cb(null, x + y);
});
return u;
};
mfn = memoize(fn, { async: true, primitive: true, maxAge: 100 });
a(mfn(3, 7, function (err, res) {
a.deep([err, res], [null, 10], "Result #1");
}), u, "Initial");
a(mfn(3, 7, function (err, res) {
a.deep([err, res], [null, 10], "Result #2");
}), u, "Initial #2");
a(mfn(5, 8, function (err, res) {
a.deep([err, res], [null, 13], "Result B #1");
}), u, "Initial #2");
a(mfn(3, 7, function (err, res) {
a.deep([err, res], [null, 10], "Result #3");
}), u, "Initial #2");
a(mfn(5, 8, function (err, res) {
a.deep([err, res], [null, 13], "Result B #2");
}), u, "Initial #3");
setTimeout(function () {
a(i, 2, "Called #2");
a(mfn(3, 7, function (err, res) {
a.deep([err, res], [null, 10], "Again: Result");
}), u, "Again: Initial");
a(mfn(5, 8, function (err, res) {
a.deep([err, res], [null, 13], "Again B: Result");
}), u, "Again B: Initial");
setTimeout(function () {
a(i, 2, "Again Called #2");
a(mfn(3, 7, function (err, res) {
a.deep([err, res], [null, 10], "Again: Result");
}), u, "Again: Initial");
a(mfn(5, 8, function (err, res) {
a.deep([err, res], [null, 13], "Again B: Result");
}), u, "Again B: Initial");
nextTick(function () {
a(i, 4, "Call After clear");
d();
});
}, 100);
}, 20);
}
}
};
};

361
test/ext/max.js Normal file
View File

@@ -0,0 +1,361 @@
'use strict';
var memoize = require('../../lib')
, nextTick = require('next-tick');
module.exports = function (a) {
return {
"Regular": {
"Sync": function (a) {
var mfn, fn, i = 0;
fn = function (x, y) {
++i;
return x + y;
};
mfn = memoize(fn, { max: 3 });
a(mfn(3, 7), 10, "Result #1");
a(i, 1, "Called #1");
a(mfn(3, 7), 10, "Result #2");
a(i, 1, "Called #2");
a(mfn(5, 8), 13, "Result B #1");
a(i, 2, "Called B #1");
a(mfn(3, 7), 10, "Result #3");
a(i, 2, "Called #3");
a(mfn(5, 8), 13, "Result B #2");
a(i, 2, "Called B #2");
a(mfn(12, 4), 16, "Result C #1");
a(i, 3, "Called C #1");
a(mfn(3, 7), 10, "Result #4");
a(i, 3, "Called #4");
a(mfn(5, 8), 13, "Result B #3");
a(i, 3, "Called B #3");
a(mfn(77, 11), 88, "Result D #1"); // Clear 12, 4
a(i, 4, "Called D #1");
a(mfn(5, 8), 13, "Result B #4");
a(i, 4, "Called B #4");
a(mfn(12, 4), 16, "Result C #2"); // Clear 3, 7
a(i, 5, "Called C #2");
a(mfn(3, 7), 10, "Result #5"); // Clear 77, 11
a(i, 6, "Called #5");
a(mfn(77, 11), 88, "Result D #2"); // Clear 5, 8
a(i, 7, "Called D #2");
a(mfn(12, 4), 16, "Result C #3");
a(i, 7, "Called C #3");
a(mfn(5, 8), 13, "Result B #5"); // Clear 3, 7
a(i, 8, "Called B #5");
a(mfn(77, 11), 88, "Result D #3");
a(i, 8, "Called D #3");
mfn.clear(77, 11);
a(mfn(77, 11), 88, "Result D #4");
a(i, 9, "Called D #4");
mfn.clearAll();
a(mfn(5, 8), 13, "Result B #6");
a(i, 10, "Called B #6");
a(mfn(77, 11), 88, "Result D #5");
a(i, 11, "Called D #5");
},
"Async": function (a, d) {
var mfn, fn, u = {}, i = 0;
fn = function (x, y, cb) {
nextTick(function () {
++i;
cb(null, x + y);
});
return u;
};
mfn = memoize(fn, { async: true, max: 3 });
a(mfn(3, 7, function (err, res) {
a.deep([err, res], [null, 10], "Result #1");
a(i, 1, "Called #1");
a(mfn(3, 7, function (err, res) {
a.deep([err, res], [null, 10], "Result #2");
a(i, 1, "Called #2");
a(mfn(5, 8, function (err, res) {
a.deep([err, res], [null, 13], "Result B #1");
a(i, 2, "Called B #1");
a(mfn(3, 7, function (err, res) {
a.deep([err, res], [null, 10], "Result #3");
a(i, 2, "Called #3");
a(mfn(5, 8, function (err, res) {
a.deep([err, res], [null, 13], "Result B #2");
a(i, 2, "Called B #2");
a(mfn(12, 4, function (err, res) {
a.deep([err, res], [null, 16], "Result C #1");
a(i, 3, "Called C #1");
a(mfn(3, 7, function (err, res) {
a.deep([err, res], [null, 10], "Result #4");
a(i, 3, "Called #4");
a(mfn(5, 8, function (err, res) {
a.deep([err, res], [null, 13], "Result B #3");
a(i, 3, "Called B #3");
a(mfn(77, 11, function (err, res) {
a.deep([err, res], [null, 88], "Result D #1");
a(i, 4, "Called D #1");
a(mfn(5, 8, function (err, res) {
a.deep([err, res], [null, 13], "Result B #4");
a(i, 4, "Called B #4");
a(mfn(12, 4, function (err, res) {
a.deep([err, res], [null, 16], "Result C #2");
a(i, 5, "Called C #2");
a(mfn(3, 7, function (err, res) {
a.deep([err, res], [null, 10], "Result #5");
a(i, 6, "Called #5");
a(mfn(77, 11, function (err, res) {
a.deep([err, res], [null, 88],
"Result D #2");
a(i, 7, "Called D #2");
a(mfn(12, 4, function (err, res) {
a.deep([err, res], [null, 16],
"Result C #3");
a(i, 7, "Called C #3");
a(mfn(5, 8, function (err, res) {
a.deep([err, res], [null, 13],
"Result B #5");
a(i, 8, "Called B #5");
a(mfn(77, 11, function (err, res) {
a.deep([err, res], [null, 88],
"Result D #3");
a(i, 8, "Called D #3");
mfn.clear(77, 11);
a(mfn(77, 11, function (err, res) {
a.deep([err, res], [null, 88],
"Result D #4");
a(i, 9, "Called D #4");
mfn.clearAll();
a(mfn(5, 8, function (err, res) {
a.deep([err, res], [null, 13],
"Result B #6");
a(i, 10, "Called B #6");
a(mfn(77, 11,
function (err, res) {
a.deep([err, res], [null, 88],
"Result D #5");
a(i, 11, "Called D #5");
d();
}), u, "Initial D #5");
}), u, "Initial B #6");
}), u, "Initial D #4");
}), u, "Initial D #3");
}), u, "Initial B #5");
}), u, "Initial C #3");
}), u, "Initial D #2");
}), u, "Initial #5");
}), u, "Initial C #2");
}), u, "Initial B #4");
}), u, "Initial D #1");
}), u, "Initial B #3");
}), u, "Initial #4");
}), u, "Initial C #1");
}), u, "Initial B #2");
}), u, "Initial #3");
}), u, "Initial B #1");
}), u, "Initial #2");
}), u, "Initial #1");
}
},
"Primitive": {
"Sync": function (a) {
var mfn, fn, i = 0;
fn = function (x, y) {
++i;
return x + y;
};
mfn = memoize(fn, { primitive: true, max: 3 });
a(mfn(3, 7), 10, "Result #1");
a(i, 1, "Called #1");
a(mfn(3, 7), 10, "Result #2");
a(i, 1, "Called #2");
a(mfn(5, 8), 13, "Result B #1");
a(i, 2, "Called B #1");
a(mfn(3, 7), 10, "Result #3");
a(i, 2, "Called #3");
a(mfn(5, 8), 13, "Result B #2");
a(i, 2, "Called B #2");
a(mfn(12, 4), 16, "Result C #1");
a(i, 3, "Called C #1");
a(mfn(3, 7), 10, "Result #4");
a(i, 3, "Called #4");
a(mfn(5, 8), 13, "Result B #3");
a(i, 3, "Called B #3");
a(mfn(77, 11), 88, "Result D #1"); // Clear 12, 4
a(i, 4, "Called D #1");
a(mfn(5, 8), 13, "Result B #4");
a(i, 4, "Called B #4");
a(mfn(12, 4), 16, "Result C #2"); // Clear 3, 7
a(i, 5, "Called C #2");
a(mfn(3, 7), 10, "Result #5"); // Clear 77, 11
a(i, 6, "Called #5");
a(mfn(77, 11), 88, "Result D #2"); // Clear 5, 8
a(i, 7, "Called D #2");
a(mfn(12, 4), 16, "Result C #3");
a(i, 7, "Called C #3");
a(mfn(5, 8), 13, "Result B #5"); // Clear 3, 7
a(i, 8, "Called B #5");
a(mfn(77, 11), 88, "Result D #3");
a(i, 8, "Called D #3");
mfn.clear(77, 11);
a(mfn(77, 11), 88, "Result D #4");
a(i, 9, "Called D #4");
mfn.clearAll();
a(mfn(5, 8), 13, "Result B #6");
a(i, 10, "Called B #6");
a(mfn(77, 11), 88, "Result D #5");
a(i, 11, "Called D #5");
},
"Async": function (a, d) {
var mfn, fn, u = {}, i = 0;
fn = function (x, y, cb) {
nextTick(function () {
++i;
cb(null, x + y);
});
return u;
};
mfn = memoize(fn, { async: true, primitive: true, max: 3 });
a(mfn(3, 7, function (err, res) {
a.deep([err, res], [null, 10], "Result #1");
a(i, 1, "Called #1");
a(mfn(3, 7, function (err, res) {
a.deep([err, res], [null, 10], "Result #2");
a(i, 1, "Called #2");
a(mfn(5, 8, function (err, res) {
a.deep([err, res], [null, 13], "Result B #1");
a(i, 2, "Called B #1");
a(mfn(3, 7, function (err, res) {
a.deep([err, res], [null, 10], "Result #3");
a(i, 2, "Called #3");
a(mfn(5, 8, function (err, res) {
a.deep([err, res], [null, 13], "Result B #2");
a(i, 2, "Called B #2");
a(mfn(12, 4, function (err, res) {
a.deep([err, res], [null, 16], "Result C #1");
a(i, 3, "Called C #1");
a(mfn(3, 7, function (err, res) {
a.deep([err, res], [null, 10], "Result #4");
a(i, 3, "Called #4");
a(mfn(5, 8, function (err, res) {
a.deep([err, res], [null, 13], "Result B #3");
a(i, 3, "Called B #3");
a(mfn(77, 11, function (err, res) {
a.deep([err, res], [null, 88], "Result D #1");
a(i, 4, "Called D #1");
a(mfn(5, 8, function (err, res) {
a.deep([err, res], [null, 13], "Result B #4");
a(i, 4, "Called B #4");
a(mfn(12, 4, function (err, res) {
a.deep([err, res], [null, 16], "Result C #2");
a(i, 5, "Called C #2");
a(mfn(3, 7, function (err, res) {
a.deep([err, res], [null, 10], "Result #5");
a(i, 6, "Called #5");
a(mfn(77, 11, function (err, res) {
a.deep([err, res], [null, 88],
"Result D #2");
a(i, 7, "Called D #2");
a(mfn(12, 4, function (err, res) {
a.deep([err, res], [null, 16],
"Result C #3");
a(i, 7, "Called C #3");
a(mfn(5, 8, function (err, res) {
a.deep([err, res], [null, 13],
"Result B #5");
a(i, 8, "Called B #5");
a(mfn(77, 11, function (err, res) {
a.deep([err, res], [null, 88],
"Result D #3");
a(i, 8, "Called D #3");
mfn.clear(77, 11);
a(mfn(77, 11, function (err, res) {
a.deep([err, res], [null, 88],
"Result D #4");
a(i, 9, "Called D #4");
mfn.clearAll();
a(mfn(5, 8, function (err, res) {
a.deep([err, res], [null, 13],
"Result B #6");
a(i, 10, "Called B #6");
a(mfn(77, 11,
function (err, res) {
a.deep([err, res], [null, 88],
"Result D #5");
a(i, 11, "Called D #5");
d();
}), u, "Initial D #5");
}), u, "Initial B #6");
}), u, "Initial D #4");
}), u, "Initial D #3");
}), u, "Initial B #5");
}), u, "Initial C #3");
}), u, "Initial D #2");
}), u, "Initial #5");
}), u, "Initial C #2");
}), u, "Initial B #4");
}), u, "Initial D #1");
}), u, "Initial B #3");
}), u, "Initial #4");
}), u, "Initial C #1");
}), u, "Initial B #2");
}), u, "Initial #3");
}), u, "Initial B #1");
}), u, "Initial #2");
}), u, "Initial #1");
}
}
};
};

34
test/ext/method.js Normal file
View File

@@ -0,0 +1,34 @@
'use strict';
var memoize = require('../../lib');
module.exports = function (a) {
return {
"No descriptor": function (a) {
var mfn, x = {}, i = 0, fn = function () {
++i;
return this;
};
mfn = memoize(fn, { method: 'foo' });
a(mfn.call(x), x, "Context");
a(x.foo(), x, "Method");
a(i, 1, "Cached");
},
"Descriptor": function (a) {
var mfn, x = {}, i = 0, fn = function () {
++i;
return this;
};
mfn = memoize(fn,
{ method: { name: 'foo', descriptor: { configurable: true } } });
a(mfn.call(x), x, "Context");
a.deep(Object.getOwnPropertyDescriptor(x, 'foo'),
{ enumerable: false, configurable: true, writable: false,
value: x.foo });
a(x.foo(), x, "Method");
a(i, 1, "Cached");
}
};
};

View File

@@ -1,9 +1,9 @@
'use strict';
var memoize = require('../lib/memoize');
var memoize = require('../../lib');
module.exports = function (t, a) {
a(typeof memoize._profile, 'function', "Set on memoize");
a(typeof t.statistics, 'object', "Access to statistics");
a(typeof t.log, 'function', "Access to log function");
a(typeof t.log(), 'string', "Log outputs string");
};

48
test/ext/ref-counter.js Normal file
View File

@@ -0,0 +1,48 @@
'use strict';
var memoize = require('../../lib')
module.exports = function (a) {
return {
"Regular": function (a) {
var i = 0, fn = function (x, y, z) { ++i; return x + y + z; }, mfn;
mfn = memoize(fn, { refCounter: true });
a(mfn.clearRef(3, 5, 7), null, "Clear before");
a(mfn(3, 5, 7), 15, "Initial");
a(mfn(3, 5, 7), 15, "Cache");
a(mfn.clearRef(3, 5, 7), false, "Clear #1");
mfn(3, 5, 7);
a(mfn.clearRef(3, 5, 7), false, "Clear #2");
mfn(3, 5, 7);
a(mfn.clearRef(3, 5, 7), false, "Clear #3");
mfn(3, 5, 7);
a(i, 1, "Not cleared");
a(mfn.clearRef(3, 5, 7), false, "Clear #4");
a(mfn.clearRef(3, 5, 7), true, "Clear final");
mfn(3, 5, 7);
a(i, 2, "Restarted");
mfn(3, 5, 7);
a(i, 2, "Cached again");
},
"Primitive": function (a) {
var i = 0, fn = function (x, y, z) { ++i; return x + y + z; }, mfn;
mfn = memoize(fn, { primitive: true, refCounter: true });
a(mfn.clearRef(3, 5, 7), null, "Clear before");
a(mfn(3, 5, 7), 15, "Initial");
a(mfn(3, 5, 7), 15, "Cache");
a(mfn.clearRef(3, 5, 7), false, "Clear #1");
mfn(3, 5, 7);
a(mfn.clearRef(3, 5, 7), false, "Clear #2");
mfn(3, 5, 7);
a(mfn.clearRef(3, 5, 7), false, "Clear #3");
mfn(3, 5, 7);
a(i, 1, "Not cleared");
a(mfn.clearRef(3, 5, 7), false, "Clear #4");
a(mfn.clearRef(3, 5, 7), true, "Clear final");
mfn(3, 5, 7);
a(i, 2, "Restarted");
mfn(3, 5, 7);
a(i, 2, "Cached again");
}
};
};

47
test/ext/resolvers.js Normal file
View File

@@ -0,0 +1,47 @@
'use strict';
var toArray = require('es5-ext/lib/Array/from')
, memoize = require('../../lib');
module.exports = function (a) {
return {
"Original arguments": function (a) {
var fn, mfn, x = {};
fn = function (x, y) { x = y; return toArray(mfn.args); };
mfn = memoize(fn, { resolvers: [] });
a.deep(mfn(23, 'raz', x), [23, 'raz', x]);
},
"Resolvers": function () {
var i = 0, fn, r;
fn = memoize(function () { ++i; return arguments; },
{ length: 3, resolvers: [Boolean, String] });
return {
"No args": function () {
i = 0;
a.deep(toArray(r = fn()), [false, 'undefined'], "First");
a(fn(), r, "Second");
a(fn(), r, "Third");
a(i, 1, "Called once");
},
"Some Args": function () {
var x = {};
i = 0;
a.deep(toArray(r = fn(0, 34, x, 45)), [false, '34', x, 45],
"First");
a(fn(0, 34, x, 22), r, "Second");
a(fn(0, 34, x, false), r, "Third");
a(i, 1, "Called once");
return {
"Other": function () {
a.deep(toArray(r = fn(1, 34, x, 34)),
[true, '34', x, 34], "Second");
a(fn(1, 34, x, 89), r, "Third");
a(i, 2, "Called once");
}
};
}
};
}
};
};

View File

@@ -121,7 +121,7 @@ module.exports = function (t, a) {
"Original arguments": function (a) {
var fn, mfn, x = {};
fn = function (x, y) { x = y; return toArray(mfn.args); };
mfn = t(fn);
mfn = t(fn, { resolvers: [] });
a.deep(mfn(23, 'raz', x), [23, 'raz', x]);
},
@@ -320,7 +320,7 @@ module.exports = function (t, a) {
"Async": {
"Regular": {
"Success": function (a, d) {
var mfn, fn, u = {}, i = 0;
var mfn, fn, u = {}, i = 0, invoked = 0;
fn = function (x, y, cb) {
nextTick(function () {
++i;
@@ -332,45 +332,57 @@ module.exports = function (t, a) {
mfn = t(fn, { async: true });
a(mfn(3, 7, function (err, res) {
++invoked;
a.deep([err, res], [null, 10], "Result #1");
}), u, "Initial");
a(mfn(3, 7, function (err, res) {
++invoked;
a.deep([err, res], [null, 10], "Result #2");
}), u, "Initial #2");
a(mfn(5, 8, function (err, res) {
++invoked;
a.deep([err, res], [null, 13], "Result B #1");
}), u, "Initial #2");
a(mfn(3, 7, function (err, res) {
++invoked;
a.deep([err, res], [null, 10], "Result #3");
}), u, "Initial #2");
a(mfn(5, 8, function (err, res) {
++invoked;
a.deep([err, res], [null, 13], "Result B #2");
}), u, "Initial #3");
nextTick(function () {
a(i, 2, "Called #2");
a(i, 2, "Init Called");
a(invoked, 5, "Cb Called");
a(mfn(3, 7, function (err, res) {
++invoked;
a.deep([err, res], [null, 10], "Again: Result");
}), u, "Again: Initial");
a(mfn(5, 8, function (err, res) {
++invoked;
a.deep([err, res], [null, 13], "Again B: Result");
}), u, "Again B: Initial");
nextTick(function () {
a(i, 2, "Again Called #2");
a(i, 2, "Init Called #2");
a(invoked, 7, "Cb Called #2");
mfn.clear(3, 7);
a(mfn(3, 7, function (err, res) {
++invoked;
a.deep([err, res], [null, 10], "Again: Result");
}), u, "Again: Initial");
a(mfn(5, 8, function (err, res) {
++invoked;
a.deep([err, res], [null, 13], "Again B: Result");
}), u, "Again B: Initial");
nextTick(function () {
a(i, 3, "Call After clear");
a(i, 3, "Init After clear");
a(invoked, 9, "Cb After clear");
d();
});
});
@@ -870,35 +882,35 @@ module.exports = function (t, a) {
a(mfn(5, 8), 13, "Result B #3");
a(i, 3, "Called B #3");
a(mfn(77, 11), 88, "Result D #1"); // Clear 3, 7
a(mfn(77, 11), 88, "Result D #1"); // Clear 12, 4
a(i, 4, "Called D #1");
a(mfn(5, 8), 13, "Result B #4");
a(i, 4, "Called B #4");
a(mfn(12, 4), 16, "Result C #2");
a(i, 4, "Called C #2");
a(mfn(12, 4), 16, "Result C #2"); // Clear 3, 7
a(i, 5, "Called C #2");
a(mfn(3, 7), 10, "Result #5"); // Clear 5, 8
a(i, 5, "Called #5");
a(mfn(77, 11), 88, "Result D #2");
a(i, 5, "Called D #2");
a(mfn(3, 7), 10, "Result #5"); // Clear 77, 11
a(i, 6, "Called #5");
a(mfn(77, 11), 88, "Result D #2"); // Clear 5, 8
a(i, 7, "Called D #2");
a(mfn(12, 4), 16, "Result C #3");
a(i, 5, "Called C #3");
a(i, 7, "Called C #3");
a(mfn(5, 8), 13, "Result B #5"); // Clear 12, 4
a(i, 6, "Called B #5");
a(mfn(5, 8), 13, "Result B #5"); // Clear 3, 7
a(i, 8, "Called B #5");
a(mfn(77, 11), 88, "Result D #3");
a(i, 6, "Called D #3");
a(i, 8, "Called D #3");
mfn.clear(77, 11);
a(mfn(77, 11), 88, "Result D #4");
a(i, 7, "Called D #4");
a(i, 9, "Called D #4");
mfn.clearAll();
a(mfn(5, 8), 13, "Result B #6");
a(i, 8, "Called B #6");
a(i, 10, "Called B #6");
a(mfn(77, 11), 88, "Result D #5");
a(i, 9, "Called D #5");
a(i, 11, "Called D #5");
},
"Async": function (a, d) {
var mfn, fn, u = {}, i = 0;
@@ -954,49 +966,49 @@ module.exports = function (t, a) {
a(mfn(12, 4, function (err, res) {
a.deep([err, res], [null, 16], "Result C #2");
a(i, 4, "Called C #2");
a(i, 5, "Called C #2");
a(mfn(3, 7, function (err, res) {
a.deep([err, res], [null, 10], "Result #5");
a(i, 5, "Called #5");
a(i, 6, "Called #5");
a(mfn(77, 11, function (err, res) {
a.deep([err, res], [null, 88],
"Result D #2");
a(i, 5, "Called D #2");
a(i, 7, "Called D #2");
a(mfn(12, 4, function (err, res) {
a.deep([err, res], [null, 16],
"Result C #3");
a(i, 5, "Called C #3");
a(i, 7, "Called C #3");
a(mfn(5, 8, function (err, res) {
a.deep([err, res], [null, 13],
"Result B #5");
a(i, 6, "Called B #5");
a(i, 8, "Called B #5");
a(mfn(77, 11, function (err, res) {
a.deep([err, res], [null, 88],
"Result D #3");
a(i, 6, "Called D #3");
a(i, 8, "Called D #3");
mfn.clear(77, 11);
a(mfn(77, 11, function (err, res) {
a.deep([err, res], [null, 88],
"Result D #4");
a(i, 7, "Called D #4");
a(i, 9, "Called D #4");
mfn.clearAll();
a(mfn(5, 8, function (err, res) {
a.deep([err, res], [null, 13],
"Result B #6");
a(i, 8, "Called B #6");
a(i, 10, "Called B #6");
a(mfn(77, 11,
function (err, res) {
a.deep([err, res], [null, 88],
"Result D #5");
a(i, 9, "Called D #5");
a(i, 11, "Called D #5");
d();
}), u, "Initial D #5");
@@ -1046,35 +1058,35 @@ module.exports = function (t, a) {
a(mfn(5, 8), 13, "Result B #3");
a(i, 3, "Called B #3");
a(mfn(77, 11), 88, "Result D #1");
a(mfn(77, 11), 88, "Result D #1"); // Clear 12, 4
a(i, 4, "Called D #1");
a(mfn(5, 8), 13, "Result B #4");
a(i, 4, "Called B #4");
a(mfn(12, 4), 16, "Result C #2");
a(i, 4, "Called C #2");
a(mfn(12, 4), 16, "Result C #2"); // Clear 3, 7
a(i, 5, "Called C #2");
a(mfn(3, 7), 10, "Result #5");
a(i, 5, "Called #5");
a(mfn(77, 11), 88, "Result D #2");
a(i, 5, "Called D #2");
a(mfn(3, 7), 10, "Result #5"); // Clear 77, 11
a(i, 6, "Called #5");
a(mfn(77, 11), 88, "Result D #2"); // Clear 5, 8
a(i, 7, "Called D #2");
a(mfn(12, 4), 16, "Result C #3");
a(i, 5, "Called C #3");
a(i, 7, "Called C #3");
a(mfn(5, 8), 13, "Result B #5");
a(i, 6, "Called B #5");
a(mfn(5, 8), 13, "Result B #5"); // Clear 3, 7
a(i, 8, "Called B #5");
a(mfn(77, 11), 88, "Result D #3");
a(i, 6, "Called D #3");
a(i, 8, "Called D #3");
mfn.clear(77, 11);
a(mfn(77, 11), 88, "Result D #4");
a(i, 7, "Called D #4");
a(i, 9, "Called D #4");
mfn.clearAll();
a(mfn(5, 8), 13, "Result B #6");
a(i, 8, "Called B #6");
a(i, 10, "Called B #6");
a(mfn(77, 11), 88, "Result D #5");
a(i, 9, "Called D #5");
a(i, 11, "Called D #5");
},
"Async": function (a, d) {
var mfn, fn, u = {}, i = 0;
@@ -1130,48 +1142,49 @@ module.exports = function (t, a) {
a(mfn(12, 4, function (err, res) {
a.deep([err, res], [null, 16], "Result C #2");
a(i, 4, "Called C #2");
a(i, 5, "Called C #2");
a(mfn(3, 7, function (err, res) {
a.deep([err, res], [null, 10], "Result #5");
a(i, 5, "Called #5");
a(i, 6, "Called #5");
a(mfn(77, 11, function (err, res) {
a.deep([err, res], [null, 88],
"Result D #2");
a(i, 5, "Called D #2");
a(i, 7, "Called D #2");
a(mfn(12, 4, function (err, res) {
a.deep([err, res], [null, 16],
"Result C #3");
a(i, 5, "Called C #3");
a(i, 7, "Called C #3");
a(mfn(5, 8, function (err, res) {
a.deep([err, res], [null, 13],
"Result B #5");
a(i, 6, "Called B #5");
a(i, 8, "Called B #5");
a(mfn(77, 11, function (err, res) {
a.deep([err, res], [null, 88],
"Result D #3");
a(i, 6, "Called D #3");
a(i, 8, "Called D #3");
mfn.clear(77, 11);
a(mfn(77, 11, function (err, res) {
a.deep([err, res], [null, 88],
"Result D #4");
a(i, 7, "Called D #4");
a(i, 9, "Called D #4");
mfn.clearAll();
a(mfn(5, 8, function (err, res) {
a.deep([err, res], [null, 13],
"Result B #6");
a(i, 8, "Called B #6");
a(i, 10, "Called B #6");
a(mfn(77, 11,
function (err, res) {
a.deep([err, res], [null, 88],
"Result D #5");
a(i, 9, "Called D #5");
a(i, 11, "Called D #5");
d();
}), u, "Initial D #5");

41
test/primitive.js Normal file
View File

@@ -0,0 +1,41 @@
'use strict';
module.exports = function (t, a) {
return {
"No args": function (a) {
var i = 0, fn = function () { ++i; return arguments[0]; }, mfn;
mfn = t(fn, { primitive: true });
a(mfn('ble'), 'ble', "#1");
a(mfn({}), 'ble', "#2");
a(i, 1, "Called once");
},
"One arg": function (a) {
var i = 0, fn = function (x) { ++i; return x; }, mfn
, y = { toString: function () { return 'foo'; } };
mfn = t(fn, { primitive: true });
a(mfn(y), y, "#1");
a(mfn('foo'), y, "#2");
a(i, 1, "Called once");
},
"Many args": function (a) {
var i = 0, fn = function (x, y, z) { ++i; return x + y + z; }, mfn
, y = { toString: function () { return 'foo'; } };
mfn = t(fn, { primitive: true });
a(mfn(y, 'bar', 'zeta'), 'foobarzeta', "#1");
a(mfn('foo', 'bar', 'zeta'), 'foobarzeta', "#2");
a(i, 1, "Called once");
},
"Clear cache": function (a) {
var i = 0, fn = function (x, y, z) { ++i; return x + y + z; }, mfn
, y = { toString: function () { return 'foo'; } };
mfn = t(fn, { primitive: true });
a(mfn(y, 'bar', 'zeta'), 'foobarzeta', "#1");
a(mfn('foo', 'bar', 'zeta'), 'foobarzeta', "#2");
a(i, 1, "Called once");
mfn.clear('foo', { toString: function () { return 'bar'; } },
'zeta');
a(mfn(y, 'bar', 'zeta'), 'foobarzeta', "#3");
a(i, 2, "Called twice");
}
};
};

175
test/regular.js Normal file
View File

@@ -0,0 +1,175 @@
'use strict';
var toArray = require('es5-ext/lib/Array/from');
module.exports = function (t, a) {
return {
"0": function () {
var i = 0, fn = function () { ++i; return 3; };
fn = t(fn);
a(fn(), 3, "First");
a(fn(1), 3, "Second");
a(fn(5), 3, "Third");
a(i, 1, "Called once");
},
"1": function () {
var i = 0, fn = function (x) { ++i; return x; };
fn = t(fn);
return {
"No arg": function () {
i = 0;
a(fn(), undefined, "First");
a(fn(), undefined, "Second");
a(fn(), undefined, "Third");
a(i, 1, "Called once");
},
"Arg": function () {
var x = {};
i = 0;
a(fn(x, 8), x, "First");
a(fn(x, 4), x, "Second");
a(fn(x, 2), x, "Third");
a(i, 1, "Called once");
},
"Other Arg": function () {
var x = {};
i = 0;
a(fn(x, 2), x, "First");
a(fn(x, 9), x, "Second");
a(fn(x, 3), x, "Third");
a(i, 1, "Called once");
}
};
},
"3": function () {
var i = 0, fn = function (x, y, z) { ++i; return [x, y, z]; }, r;
fn = t(fn);
return {
"No args": function () {
i = 0;
a.deep(r = fn(), [undefined, undefined, undefined], "First");
a(fn(), r, "Second");
a(fn(), r, "Third");
a(i, 1, "Called once");
},
"Some Args": function () {
var x = {};
i = 0;
a.deep(r = fn(x, 8), [x, 8, undefined], "First");
a(fn(x, 8), r, "Second");
a(fn(x, 8), r, "Third");
a(i, 1, "Called once");
return {
"Other": function () {
a.deep(r = fn(x, 5), [x, 5, undefined], "Second");
a(fn(x, 5), r, "Third");
a(i, 2, "Called once");
}
};
},
"Full stuff": function () {
var x = {};
i = 0;
a.deep(r = fn(x, 8, 23, 98), [x, 8, 23], "First");
a(fn(x, 8, 23, 43), r, "Second");
a(fn(x, 8, 23, 9), r, "Third");
a(i, 1, "Called once");
return {
"Other": function () {
a.deep(r = fn(x, 23, 8, 13), [x, 23, 8], "Second");
a(fn(x, 23, 8, 22), r, "Third");
a(i, 2, "Called once");
}
};
}
};
},
"Dynamic": function () {
var i = 0, fn = function () { ++i; return arguments; }, r;
fn = t(fn, { length: false });
return {
"No args": function () {
i = 0;
a.deep(toArray(r = fn()), [], "First");
a(fn(), r, "Second");
a(fn(), r, "Third");
a(i, 1, "Called once");
},
"Some Args": function () {
var x = {};
i = 0;
a.deep(toArray(r = fn(x, 8)), [x, 8], "First");
a(fn(x, 8), r, "Second");
a(fn(x, 8), r, "Third");
a(i, 1, "Called once");
},
"Many args": function () {
var x = {};
i = 0;
a.deep(toArray(r = fn(x, 8, 23, 98)), [x, 8, 23, 98], "First");
a(fn(x, 8, 23, 98), r, "Second");
a(fn(x, 8, 23, 98), r, "Third");
a(i, 1, "Called once");
}
};
},
"Clear Cache": {
"Specific": function () {
var i = 0, fn, mfn, x = {};
fn = function (a, b, c) {
if (c === 3) {
++i;
}
return arguments;
};
mfn = t(fn);
mfn(1, x, 3);
mfn(1, x, 4);
mfn.clear(1, x, 4);
mfn(1, x, 3);
mfn(1, x, 3);
a(i, 1, "Pre clear");
mfn.clear(1, x, 3);
mfn(1, x, 3);
a(i, 2, "After clear");
i = 0;
mfn = t(fn, { length: false });
mfn(1, x, 3);
mfn(1, x, 3);
mfn();
mfn();
mfn.clear();
mfn(1, x, 3);
a(i, 1, "Proper no arguments clear");
},
"All": function () {
var i = 0, fn, x = {};
fn = function () {
++i;
return arguments;
};
fn = t(fn, { length: 3 });
fn(1, x, 3);
fn(1, x, 4);
fn(1, x, 3);
fn(1, x, 4);
a(i, 2, "Pre clear");
fn.clearAll();
fn(1, x, 3);
fn(1, x, 4);
fn(1, x, 3);
fn(1, x, 4);
a(i, 4, "After clear");
}
}
};
};