From c487b4f6dcbfa35f60c575b5049321bf4be06534 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Evandro=20Leopoldino=20Gon=C3=A7alves?= Date: Sun, 18 Apr 2021 12:13:49 +0200 Subject: [PATCH 1/3] implement cancel method to throttle --- packages/function-throttle/index.js | 32 +++++++++++++++++++++-------- test/function-throttle/index.js | 15 ++++++++++++++ 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/packages/function-throttle/index.js b/packages/function-throttle/index.js index bdb49b110..8cb5f256b 100644 --- a/packages/function-throttle/index.js +++ b/packages/function-throttle/index.js @@ -1,35 +1,51 @@ module.exports = throttle; function throttle(fn, interval, options) { - var wait = false; + var timeoutId = null; var leading = (options && options.leading); var trailing = (options && options.trailing); + if (leading == null) { leading = true; // default } + if (trailing == null) { trailing = !leading; //default } + if (leading == true) { trailing = false; // forced because there should be invocation per call } - return function() { - var callNow = leading && !wait; + var cancel = function() { + if (timeoutId) { + clearTimeout(timeoutId); + timeoutId = null; + } + }; + + var throttleWrapper = function() { + var callNow = leading && !timeoutId; var context = this; var args = arguments; - if (!wait) { - wait = true; - setTimeout(function() { - wait = false; + + if (!timeoutId) { + timeoutId = setTimeout(function() { + timeoutId = null; + if (trailing) { return fn.apply(context, args); } }, interval); } + if (callNow) { callNow = false; - return fn.apply(this, arguments); + return fn.apply(context, args); } }; + + throttleWrapper.cancel = cancel; + + return throttleWrapper; } diff --git a/test/function-throttle/index.js b/test/function-throttle/index.js index 052754eeb..ca0b8473c 100644 --- a/test/function-throttle/index.js +++ b/test/function-throttle/index.js @@ -392,3 +392,18 @@ test('invokes repeatedly when wait is falsey', function(t) { }, 40); }, 40); }); + +test('cancel delayed function', function(t) { + t.plan(1); + + var callCounter = 0; + var fn = throttle(function() { + callCounter++; + }, 200); + + fn.cancel(); + + setTimeout(function() { + t.equal(callCounter, 0); + }, 400); +}); From 0766398be1da18215bd5c47b4f35f150e7e9c082 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Evandro=20Leopoldino=20Gon=C3=A7alves?= Date: Fri, 23 Jul 2021 10:59:58 +0200 Subject: [PATCH 2/3] add logic to limit the maximum number of items cached --- packages/function-memoize/index.js | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/function-memoize/index.js b/packages/function-memoize/index.js index 65b565939..461a5c7e7 100644 --- a/packages/function-memoize/index.js +++ b/packages/function-memoize/index.js @@ -23,26 +23,35 @@ module.exports = memoize; sum(10, 20) -- Cache hit! */ -function memoize(callback, resolver) { +function memoize(callback, options, resolver) { if (typeof callback !== 'function') { throw new Error('`callback` should be a function'); } + if (options !== undefined && typeof options !== 'object') { + throw new Error('`options` should be an object'); + } + if (resolver !== undefined && typeof resolver !== 'function') { throw new Error('`resolver` should be a function'); } - var cache = {}; + var cache = new Map(); var memoized = function() { var args = Array.prototype.slice.call(arguments); // to simplify JSON.stringify var key = resolver ? resolver.apply(this, args) : JSON.stringify(args); - if (!(key in cache)) { - cache[key] = callback.apply(this, args); + if (!cache.has(key)) { + cache.set(key, callback.apply(this, args)); + } + + if (typeof options.max === 'number' && cache.size > options.max) { + var firstKey = cache.keys()[0]; + cache.delete(firstKey); } - return cache[key]; + return cache.get(key); }; memoized.cache = cache; From 78c57cafd30cb81080f47b740a835ae3682d2cf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Evandro=20Leopoldino=20Gon=C3=A7alves?= Date: Mon, 9 Aug 2021 10:43:04 +0200 Subject: [PATCH 3/3] implements specific solution for environments that don't implement Map --- packages/function-memoize/index.js | 45 ++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/packages/function-memoize/index.js b/packages/function-memoize/index.js index 461a5c7e7..0439851c3 100644 --- a/packages/function-memoize/index.js +++ b/packages/function-memoize/index.js @@ -23,20 +23,53 @@ module.exports = memoize; sum(10, 20) -- Cache hit! */ -function memoize(callback, options, resolver) { +function Order() { + this.keys = []; + this.cache = {}; +} + +Order.prototype = { + 'has': function(key) { + return this.cache.hasOwnProperty(key); + }, + + 'get': function(key) { + return this.cache[key]; + }, + + 'set': function(key, value) { + if (!this.has(key)) { + this.keys.push(key); + } + + this.cache[key] = value; + }, + + 'delete': function(key) { + var index = this.keys.indexOf(key); + this.keys.splice(index, 1); + delete this.cache[key]; + }, + + 'keys': function() { + return this.keys; + }, +}; + +function memoize(callback, resolver, options) { if (typeof callback !== 'function') { throw new Error('`callback` should be a function'); } - if (options !== undefined && typeof options !== 'object') { - throw new Error('`options` should be an object'); - } - if (resolver !== undefined && typeof resolver !== 'function') { throw new Error('`resolver` should be a function'); } - var cache = new Map(); + if (options !== undefined && typeof options !== 'object') { + throw new Error('`options` should be an object'); + } + + var cache = 'Map' in (typeof window === 'undefined' ? global : window) ? new Map() : new Order(); var memoized = function() { var args = Array.prototype.slice.call(arguments); // to simplify JSON.stringify