From e06d11dcb2c2c10c2be4446f2875846f20d3f64d Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Fri, 29 May 2015 15:50:30 -0700 Subject: [PATCH] [Core] Add throttle service Add service for throttling function calls; specifically supports reducing tick mark recalculation, WTD-1202. --- platform/core/bundle.json | 5 ++ platform/core/src/services/Throttle.js | 63 +++++++++++++++++++++ platform/core/test/services/ThrottleSpec.js | 49 ++++++++++++++++ platform/core/test/suite.json | 1 + 4 files changed, 118 insertions(+) create mode 100644 platform/core/src/services/Throttle.js create mode 100644 platform/core/test/services/ThrottleSpec.js diff --git a/platform/core/bundle.json b/platform/core/bundle.json index 5a2727eb75..3f7376f3f3 100644 --- a/platform/core/bundle.json +++ b/platform/core/bundle.json @@ -180,6 +180,11 @@ { "key": "now", "implementation": "services/Now.js" + }, + { + "key": "throttle", + "implementation": "services/Throttle.js", + "depends": [ "$timeout" ] } ], "roots": [ diff --git a/platform/core/src/services/Throttle.js b/platform/core/src/services/Throttle.js new file mode 100644 index 0000000000..619f53161e --- /dev/null +++ b/platform/core/src/services/Throttle.js @@ -0,0 +1,63 @@ +/*global define*/ + +define( + [], + function () { + "use strict"; + + /** + * Throttler for function executions, registered as the `throttle` + * service. + * + * Usage: + * + * throttle(fn, delay, [apply]) + * + * Returns a function that, when invoked, will invoke `fn` after + * `delay` milliseconds, only if no other invocations are pending. + * The optional argument `apply` determines whether. + * + * The returned function will itself return a `Promise` which will + * resolve to the returned value of `fn` whenever that is invoked. + * + * @returns {Function} + */ + function Throttle($timeout) { + /** + * Throttle this function. + * @param {Function} fn the function to throttle + * @param {number} [delay] the delay, in milliseconds, before + * executing this function; defaults to 0. + * @param {boolean} apply true if a `$apply` call should be + * invoked after this function executes; defaults to + * `false`. + */ + return function (fn, delay, apply) { + var activeTimeout; + + // Clear active timeout, so that next invocation starts + // a new one. + function clearActiveTimeout() { + activeTimeout = undefined; + } + + // Defaults + delay = delay || 0; + apply = apply || false; + + return function () { + // Start a timeout if needed + if (!activeTimeout) { + activeTimeout = $timeout(fn, delay, apply); + activeTimeout.then(clearActiveTimeout); + } + // Return whichever timeout is active (to get + // a promise for the results of fn) + return activeTimeout; + }; + }; + } + + return Throttle; + } +); \ No newline at end of file diff --git a/platform/core/test/services/ThrottleSpec.js b/platform/core/test/services/ThrottleSpec.js new file mode 100644 index 0000000000..ccd6644eb7 --- /dev/null +++ b/platform/core/test/services/ThrottleSpec.js @@ -0,0 +1,49 @@ +/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/ + +define( + ["../../src/services/Throttle"], + function (Throttle) { + "use strict"; + + describe("The 'throttle' service", function () { + var throttle, + mockTimeout, + mockFn, + mockPromise; + + beforeEach(function () { + mockTimeout = jasmine.createSpy("$timeout"); + mockPromise = jasmine.createSpyObj("promise", ["then"]); + mockFn = jasmine.createSpy("fn"); + mockTimeout.andReturn(mockPromise); + throttle = new Throttle(mockTimeout); + }); + + it("provides functions which run on a timeout", function () { + var throttled = throttle(mockFn); + // Verify precondition: Not called at throttle-time + expect(mockTimeout).not.toHaveBeenCalled(); + expect(throttled()).toEqual(mockPromise); + expect(mockTimeout).toHaveBeenCalledWith(mockFn, 0, false); + }); + + it("schedules only one timeout at a time", function () { + var throttled = throttle(mockFn); + throttled(); + throttled(); + throttled(); + expect(mockTimeout.calls.length).toEqual(1); + }); + + it("schedules additional invocations after resolution", function () { + var throttled = throttle(mockFn); + throttled(); + mockPromise.then.mostRecentCall.args[0](); // Resolve timeout + throttled(); + mockPromise.then.mostRecentCall.args[0](); + throttled(); + expect(mockTimeout.calls.length).toEqual(3); + }); + }); + } +); \ No newline at end of file diff --git a/platform/core/test/suite.json b/platform/core/test/suite.json index 36f3e81980..5fd8f97810 100644 --- a/platform/core/test/suite.json +++ b/platform/core/test/suite.json @@ -23,6 +23,7 @@ "objects/DomainObjectProvider", "services/Now", + "services/Throttle", "types/MergeModels", "types/TypeCapability",