UNPKG

google-closure-library

Version:
167 lines (147 loc) 4.25 kB
/** * @license * Copyright The Closure Library Authors. * SPDX-License-Identifier: Apache-2.0 */ /** * @fileoverview Definition of the goog.async.Throttle class. * * @see ../demos/timers.html */ goog.module('goog.async.Throttle'); goog.module.declareLegacyNamespace(); const Disposable = goog.require('goog.Disposable'); const Timer = goog.require('goog.Timer'); /** * Throttle will perform an action that is passed in no more than once * per interval (specified in milliseconds). If it gets multiple signals * to perform the action while it is waiting, it will only perform the action * once at the end of the interval. * @final * @template T */ class Throttle extends Disposable { /** * @param {function(this: T, ...?)} listener Function to callback when the * action is triggered. * @param {number} interval Interval over which to throttle. The listener can * only be called once per interval. * @param {T=} handler Object in whose scope to call the listener. */ constructor(listener, interval, handler) { super(); /** * Function to callback * @type {function(this: T, ...?)} * @private */ this.listener_ = handler != null ? listener.bind(handler) : listener; /** * Interval for the throttle time * @type {number} * @private */ this.interval_ = interval; /** * The last arguments passed into `fire`, or null if there is no pending * call. * @private {?IArrayLike} */ this.args_ = null; /** * Indicates that the action is pending and needs to be fired. * @type {boolean} * @private */ this.shouldFire_ = false; /** * Indicates the count of nested pauses currently in effect on the throttle. * When this count is not zero, fired actions will be postponed until the * throttle is resumed enough times to drop the pause count to zero. * @type {number} * @private */ this.pauseCount_ = 0; /** * Timer for scheduling the next callback * @type {?number} * @private */ this.timer_ = null; } /** * Notifies the throttle that the action has happened. It will throttle * the call so that the callback is not called too often according to the * interval parameter passed to the constructor, passing the arguments * from the last call of this function into the throttled function. * @param {...?} var_args Arguments to pass on to the throttled function. */ fire(var_args) { this.args_ = arguments; if (!this.timer_ && !this.pauseCount_) { this.doAction_(); } else { this.shouldFire_ = true; } } /** * Cancels any pending action callback. The throttle can be restarted by * calling {@link #fire}. */ stop() { if (this.timer_) { Timer.clear(this.timer_); this.timer_ = null; this.shouldFire_ = false; this.args_ = null; } } /** * Pauses the throttle. All pending and future action callbacks will be * delayed until the throttle is resumed. Pauses can be nested. */ pause() { this.pauseCount_++; } /** * Resumes the throttle. If doing so drops the pausing count to zero, * pending action callbacks will be executed as soon as possible, but * still no sooner than an interval's delay after the previous call. * Future action callbacks will be executed as normal. */ resume() { this.pauseCount_--; if (!this.pauseCount_ && this.shouldFire_ && !this.timer_) { this.shouldFire_ = false; this.doAction_(); } } /** @override */ disposeInternal() { super.disposeInternal(); this.stop(); } /** * Handler for the timer to fire the throttle * @private */ onTimer_() { this.timer_ = null; if (this.shouldFire_ && !this.pauseCount_) { this.shouldFire_ = false; this.doAction_(); } } /** * Calls the callback * @private */ doAction_() { this.timer_ = Timer.callOnce(() => this.onTimer_(), this.interval_); const args = this.args_; // release memory first so it always happens even if listener throws this.args_ = null; this.listener_.apply(null, args); } } exports = Throttle;