component-basepackage.src.debounce.js Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of vaadin-webcomponents Show documentation
Show all versions of vaadin-webcomponents Show documentation
Mvnpm composite: Vaadin webcomponents
/**
@license
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
const debouncerQueue = new Set();
/**
* @summary Collapse multiple callbacks into one invocation after a timer.
*/
export class Debouncer {
/**
* Creates a debouncer if no debouncer is passed as a parameter
* or it cancels an active debouncer otherwise. The following
* example shows how a debouncer can be called multiple times within a
* microtask and "debounced" such that the provided callback function is
* called once. Add this method to a custom element:
*
* ```js
* import {microTask} from '@vaadin/component-base/src/async.js';
* import {Debouncer} from '@vaadin/component-base/src/debounce.js';
* // ...
*
* _debounceWork() {
* this._debounceJob = Debouncer.debounce(this._debounceJob,
* microTask, () => this._doWork());
* }
* ```
*
* If the `_debounceWork` method is called multiple times within the same
* microtask, the `_doWork` function will be called only once at the next
* microtask checkpoint.
*
* Note: In testing it is often convenient to avoid asynchrony. To accomplish
* this with a debouncer, you can use `enqueueDebouncer` and
* `flush`. For example, extend the above example by adding
* `enqueueDebouncer(this._debounceJob)` at the end of the
* `_debounceWork` method. Then in a test, call `flush` to ensure
* the debouncer has completed.
*
* @param {Debouncer?} debouncer Debouncer object.
* @param {!AsyncInterface} asyncModule Object with Async interface
* @param {function()} callback Callback to run.
* @return {!Debouncer} Returns a debouncer object.
*/
static debounce(debouncer, asyncModule, callback) {
if (debouncer instanceof Debouncer) {
// Cancel the async callback, but leave in debouncerQueue if it was
// enqueued, to maintain 1.x flush order
debouncer._cancelAsync();
} else {
debouncer = new Debouncer();
}
debouncer.setConfig(asyncModule, callback);
return debouncer;
}
constructor() {
this._asyncModule = null;
this._callback = null;
this._timer = null;
}
/**
* Sets the scheduler; that is, a module with the Async interface,
* a callback and optional arguments to be passed to the run function
* from the async module.
*
* @param {!AsyncInterface} asyncModule Object with Async interface.
* @param {function()} callback Callback to run.
* @return {void}
*/
setConfig(asyncModule, callback) {
this._asyncModule = asyncModule;
this._callback = callback;
this._timer = this._asyncModule.run(() => {
this._timer = null;
debouncerQueue.delete(this);
this._callback();
});
}
/**
* Cancels an active debouncer and returns a reference to itself.
*
* @return {void}
*/
cancel() {
if (this.isActive()) {
this._cancelAsync();
// Canceling a debouncer removes its spot from the flush queue,
// so if a debouncer is manually canceled and re-debounced, it
// will reset its flush order (this is a very minor difference from 1.x)
// Re-debouncing via the `debounce` API retains the 1.x FIFO flush order
debouncerQueue.delete(this);
}
}
/**
* Cancels a debouncer's async callback.
*
* @return {void}
*/
_cancelAsync() {
if (this.isActive()) {
this._asyncModule.cancel(/** @type {number} */ (this._timer));
this._timer = null;
}
}
/**
* Flushes an active debouncer and returns a reference to itself.
*
* @return {void}
*/
flush() {
if (this.isActive()) {
this.cancel();
this._callback();
}
}
/**
* Returns true if the debouncer is active.
*
* @return {boolean} True if active.
*/
isActive() {
return this._timer != null;
}
}
/**
* Adds a `Debouncer` to a list of globally flushable tasks.
*
* @param {!Debouncer} debouncer Debouncer to enqueue
* @return {void}
*/
export function enqueueDebouncer(debouncer) {
debouncerQueue.add(debouncer);
}
/**
* Flushes any enqueued debouncers
*
* @return {boolean} Returns whether any debouncers were flushed
*/
export function flushDebouncers() {
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;
});
}
});
return didFlush;
}
export const flush = () => {
let debouncers;
do {
debouncers = flushDebouncers();
} while (debouncers);
};