Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[3.x] Use set and clear debouncer upon completion. Fixes #5250. #5499

Merged
merged 9 commits into from
Feb 28, 2019
40 changes: 40 additions & 0 deletions lib/utils/debounce.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export class Debouncer {
if (this.isActive()) {
this._asyncModule.cancel(/** @type {number} */(this._timer));
this._timer = null;
debouncerQueue.delete(this);
kevinpschaaf marked this conversation as resolved.
Show resolved Hide resolved
}
}
/**
Expand Down Expand Up @@ -112,3 +113,42 @@ 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) {
// Re-enqueued debouncers are put at the end of the queue; for Set, this
// means removing and re-adding, since forEach traverses insertion order
if (debouncerQueue.has(debouncer)) {
kevinpschaaf marked this conversation as resolved.
Show resolved Hide resolved
debouncerQueue.delete(debouncer);
}
debouncerQueue.add(debouncer);
};

/**
* Flushes any enqueued debouncers
*
* @return {void}
*/
export const flushDebouncers = function() {
const didFlush = Boolean(debouncerQueue.size);
// If new debouncers are added while flushing, Set.forEach will ensure
// newly added ones are also flushed
debouncerQueue.forEach(debouncer => {
try {
debouncer.flush();
} catch(e) {
setTimeout(() => {
throw e;
});
}
});
debouncerQueue = new Set();
return didFlush;
};
28 changes: 2 additions & 26 deletions lib/utils/flush.js
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
54 changes: 54 additions & 0 deletions test/unit/debounce.html
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -209,6 +210,59 @@
});

});

suite('enqueueDebouncer & flush', function() {
kevinpschaaf marked this conversation as resolved.
Show resolved Hide resolved
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 = [];
for (let i=0; i<150; i++) {
nestedCallbacks.push(sinon.spy(() =>
actualCallbacks.push(nestedCallbacks[i])));
}
// First set of short-running debouncers
const microtaskCallbacks = [];
for (let i=0; i<150; i++) {
microtaskCallbacks.push(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);
});

});
});
</script>
</body>
Expand Down