var has = Object.prototype.hasOwnProperty function toArgString() { return [].join.call(arguments) } module.exports = function (opts, fn /*, preArgs... */) { var preArgs = [].slice.call(arguments, 2) if (typeof opts === 'function') { if (arguments.length >= 2) preArgs.unshift(fn) fn = opts opts = {} } var cache = opts.cache === false ? null : opts.cache === true || opts.cache == null ? new Storage() : opts.cache var callbacks = {/* arg: [callback] */} var toString = opts.asString || toArgString var memoized = function (/* args..., cb */) { var args = [].slice.call(arguments) var cb = args.pop() var memo = toString.apply(this, args) if (cache && cache.has(memo)) { var self = this return process.nextTick(function () { if (cache.has(memo)) cb.call(self, null, cache.get(memo)) else run.call(self, args, memo, cb) }) } run.call(this, args, memo, cb) } memoized.cache = cache return memoized function run(args, memo, cb) { if (has.call(callbacks, memo)) return callbacks[memo].push([this, cb]) var cbs = callbacks[memo] = [[this, cb]] fn.apply(this, preArgs.concat(args, function (err, result) { if (!err && cache) cache.set(memo, result) while (cbs.length) { cb = cbs.shift() cb[1].call(cb[0], err, result) } delete callbacks[memo] })) } } function Storage() { this.data = {} } Storage.prototype.has = function (key) { return has.call(this.data, key) } Storage.prototype.get = function (key) { return this.data[key] } Storage.prototype.set = function (key, value) { this.data[key] = value } Storage.prototype.remove = function (key) { delete this.data[key] } Storage.prototype.clear = function (key) { this.data = {} }