UNPKG

@exmg/lit-base

Version:
161 lines 5.45 kB
/** @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 */ import './async.js'; /** * @summary Collapse multiple callbacks into one invocation after a timer. */ export class 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; } /** * 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 '@polymer/polymer/lib/utils/async.js'; * import {Debouncer} from '@polymer/polymer/lib/utils/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; } } const 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) { debouncerQueue.add(debouncer); }; /** * Flushes any enqueued debouncers * * @return {boolean} Returns whether any debouncers were flushed */ 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; }); } }); return didFlush; }; //# sourceMappingURL=debounce.js.map