'use strict'; /*@ Ox.cache Memoize a function fn function options async function is async, last argument must be callback key return key for arguments > Ox.test.fn(10) == Ox.test.fn(10); true > Ox.test.fn(10) == Ox.test.fn.clear()(10); false @*/ // TODO: add async test Ox.cache = function(fn, options) { var cache = {}, ret; options = options || {}; options.async = options.async || false; options.key = options.key || JSON.stringify; ret = function() { var args = Ox.slice(arguments), key = options.key(args); function callback() { // cache all arguments passed to callback cache[key] = Ox.slice(arguments); // call the original callback Ox.last(args).apply(this, arguments); } if (options.async) { if (!(key in cache)) { // call function with patched callback fn.apply(this, args.slice(0, -1).concat(callback)); } else { // call callback with cached arguments setTimeout(function() { callback.apply(this, cache[key]); }); } } else { if (!(key in cache)) { cache[key] = fn.apply(this, args); } return cache[key]; } }; ret.clear = function() { if (arguments.length == 0) { cache = {}; } else { Ox.makeArray(arguments).forEach(function(key) { delete cache[key]; }); } return ret; }; return ret; }; /*@ Ox.debounce Runs a function once it stops being called for a given interval (fn[, ms][, immediate]) -> Debounced function fn Function to debounce ms Interval in milliseconds immediate If true, function is called once immediately @*/ Ox.debounce = function(fn/*, ms, immediate*/) { var args, immediate = Ox.last(arguments) === true, ms = Ox.isNumber(arguments[1]) ? arguments[1] : 250, timeout; return function() { args = arguments; if (!timeout) { if (immediate) { fn.apply(null, args); args = null; } } else { clearTimeout(timeout); } timeout = setTimeout(function() { if (args !== null) { fn.apply(null, args); } timeout = null; }, ms); }; }; /*@ Ox.identity Returns its first argument This can be used as a default iterator > Ox.identity(Infinity) Infinity @*/ Ox.identity = function(value) { return value; }; /*@ Ox.noop Returns undefined and calls optional callback without arguments This can be used as a default iterator in an asynchronous loop, or to combine a synchronous and an asynchronous code path. > Ox.noop(1, 2, 3) undefined > Ox.noop(1, 2, 3, function() { Ox.test(arguments.length, 0); }) undefined @*/ Ox.noop = function() { var callback = Ox.last(arguments); Ox.isFunction(callback) && callback(); }; /*@ Ox.once Runs a function once, and then never again (fn[, ms]) -> Function that will run only once fn Function to run once @*/ Ox.once = function(fn) { var once = false; return function() { if (!once) { once = true; fn.apply(null, args); } }; }; /*@ Ox.queue Queue of asynchronous function calls with cached results The results are cached based on all arguments to `fn`, except the last one, which is the callback. (fn, maxThreads) -> Queue function .cancel Cancels all running function calls .clear Clears the queue .reset Cancels all running function calls and clears the queue fn Queued function maxThreads Number of parallel function calls @*/ Ox.queue = function(fn, maxThreads) { var maxThreads = maxThreads || 10, processing = [], queued = [], ret = Ox.cache(function() { var args = Ox.slice(arguments); queued.push({args: args, key: getKey(args)}); process(); }, {async: true, key: getKey}), threads = 0; ret.cancel = function() { threads -= processing.length; processing = []; return ret; }; ret.clear = function() { threads = 0; queued = []; return ret; }; ret.reset = function() { return ret.cancel().clear(); }; function getKey(args) { return JSON.stringify(args.slice(0, -1)); } function process() { var n = Math.min(queued.length, maxThreads - threads); if (n) { threads += n; processing = processing.concat(queued.splice(0, n)); Ox.parallelForEach( processing, function(value, index, array, callback) { var args = value.args, key = value.key; fn.apply(this, args.slice(0, -1).concat(function(result) { var index = Ox.indexOf(processing, function(value) { return value.key == key; }); if (index > -1) { processing.splice(index, 1); args.slice(-1)[0](result); threads--; } callback(); })); }, process ); } } return ret; }; /*@ Ox.throttle Runs a function at most once per given interval (fn[, ms]) -> Throttled function fn Function to throttle ms Interval in milliseconds @*/ Ox.throttle = function(fn, ms) { var args, timeout; ms = arguments.length == 1 ? 250 : ms; return function() { args = arguments; if (!timeout) { fn.apply(null, args); args = null; timeout = setTimeout(function() { if (args !== null) { fn.apply(null, args); } timeout = null; }, ms); }; }; }; /*@ Ox.time Returns the time it takes to execute a given function (fn) -> Time in milliseconds @*/ Ox.time = function(fn) { var time = new Date(); fn(); return new Date() - time; };