diff --git a/lib/utils/debounce.js b/lib/utils/debounce.js index eac7dd0ee8..cabc24f8eb 100644 --- a/lib/utils/debounce.js +++ b/lib/utils/debounce.js @@ -47,6 +47,7 @@ export class Debouncer { if (this.isActive()) { this._asyncModule.cancel(/** @type {number} */(this._timer)); this._timer = null; + debouncerQueue.delete(this); } } /** @@ -112,3 +113,38 @@ export class Debouncer { return debouncer; } } + +let debouncerQueue = new Set(); + +/** + * Adds a `Debouncer` to a list of globally flushable tasks. + * + * @param {!Debouncer} debouncer Debouncer to enqueue + * @return {void} + */ +export const enqueueDebouncer = function(debouncer) { + if (debouncerQueue.has(debouncer)) { + debouncerQueue.delete(debouncer); + } + debouncerQueue.add(debouncer); +}; + +/** + * Flushes any enqueued debouncers + * + * @return {void} + */ +export const flushDebouncers = function() { + const didFlush = Boolean(debouncerQueue.size); + debouncerQueue.forEach(debouncer => { + try { + debouncer.flush(); + } catch(e) { + setTimeout(() => { + throw e; + }); + } + }); + debouncerQueue = new Set(); + return didFlush; +}; \ No newline at end of file diff --git a/lib/utils/flush.js b/lib/utils/flush.js index 065a58a901..a719047010 100644 --- a/lib/utils/flush.js +++ b/lib/utils/flush.js @@ -11,32 +11,8 @@ import './boot.js'; /* eslint-disable no-unused-vars */ import { Debouncer } from '../utils/debounce.js'; // used in type annotations /* eslint-enable no-unused-vars */ - -let debouncerQueue = []; - -/** - * Adds a `Debouncer` to a list of globally flushable tasks. - * - * @param {!Debouncer} debouncer Debouncer to enqueue - * @return {void} - */ -export const enqueueDebouncer = function(debouncer) { - debouncerQueue.push(debouncer); -}; - -function flushDebouncers() { - const didFlush = Boolean(debouncerQueue.length); - while (debouncerQueue.length) { - try { - debouncerQueue.shift().flush(); - } catch(e) { - setTimeout(() => { - throw e; - }); - } - } - return didFlush; -} +import { flushDebouncers } from '../utils/debounce.js'; // used in type annotations +export { enqueueDebouncer } from '../utils/debounce.js'; // used in type annotations /** * Forces several classes of asynchronously queued tasks to flush: diff --git a/test/unit/debounce.html b/test/unit/debounce.html index ece33b94a4..aed0378b5b 100644 --- a/test/unit/debounce.html +++ b/test/unit/debounce.html @@ -33,6 +33,7 @@ import { Polymer } from '../../polymer-legacy.js'; import { Debouncer } from '../../lib/utils/debounce.js'; import { microTask, timeOut, animationFrame, idlePeriod } from '../../lib/utils/async.js'; +import { enqueueDebouncer, flush } from '../../lib/utils/flush.js'; Polymer({is: 'x-basic'}); suite('debounce', function() { @@ -209,6 +210,53 @@ }); }); + + suite('enqueueDebouncer & flush', function() { + function testEnqueue(shouldFlush, done) { + // Longer-running debouncer + const timeoutCallback = sinon.spy(() => actualCallbacks.push(timeoutCallback)); + enqueueDebouncer(Debouncer.debounce(null, timeOut, timeoutCallback)); + // Set of short-running debouncers enqueued in the middle of first set + const nestedCallbacks = new Array(150).fill().map((_, i) => sinon.spy(() => + actualCallbacks.push(nestedCallbacks[i]))); + // First set of short-running debouncers + const microtaskCallbacks = new Array(150).fill().map((_, i) => sinon.spy(() => { + actualCallbacks.push(microtaskCallbacks[i]); + if (i === 125) { + nestedCallbacks.forEach(cb => + enqueueDebouncer(Debouncer.debounce(null, microTask, cb))); + } + })); + microtaskCallbacks.forEach(cb => + enqueueDebouncer(Debouncer.debounce(null, microTask, cb))); + // Expect short before long + let expectedCallbacks; + const actualCallbacks = []; + const verify = () => { + actualCallbacks.forEach(cb => assert.isTrue(cb.calledOnce)); + assert.deepEqual(expectedCallbacks, actualCallbacks); + done(); + }; + if (shouldFlush) { + expectedCallbacks = [timeoutCallback, ...microtaskCallbacks, ...nestedCallbacks]; + flush(); + // When flushing, order is order of enqueing + verify(); + } else { + expectedCallbacks = [...microtaskCallbacks, ...nestedCallbacks, timeoutCallback]; + timeOut.run(verify); + } + } + + test('non-flushed', function(done) { + testEnqueue(false, done); + }); + + test('flushed', function(done) { + testEnqueue(true, done); + }); + + }); });