text: /** @fileoverview Helpers to wrap functions. */
  
  /**
   * Creates a function that is evaluated only once and returns the cached result
   * subsequently.
   *
   * Please note that `once` only takes the function definition into account,
   * so it will return the same cached value even when the arguments are
   * different.
   *
   * @template T
   * @param {(function(...any):T?)} fn
   * @return {(function(...any):T?)}
   */
  export function once(fn) {
    let evaluated = false;
    /** @type {T?} */
    let retValue = null;
    let callback = fn;
  
    return (...args) => {
      if (!evaluated) {
        retValue = callback.apply(self, args);
        evaluated = true;
        /** @type {?} */ (callback) = null; // GC
      }
      return retValue;
    };
  }
  
  /**
   * Wraps a given callback and applies a rate limit.
   * It throttles the calls so that no consequent calls have time interval
   * smaller than the given minimal interval.
   *
   * @param {Window} win
   * @param {function(...T):R} callback
   * @param {number} minInterval the minimum time interval in millisecond
   * @return {function(...T)}
   * @template T
   * @template R
   */
  export function throttle(win, callback, minInterval) {
    let locker = 0;
  
    /** @type {T[]?} */
    let nextCallArgs = null;
  
    /**
     * @param {T[]} args
     */
    function fire(args) {
      nextCallArgs = null;
      // Lock the fire for minInterval milliseconds
      locker = win.setTimeout(waiter, minInterval);
  
      callback.apply(null, args);
    }
  
    /**
     * Waiter function
     */
    function waiter() {
      locker = 0;
      // If during the period there're invocations queued up, fire once.
      if (nextCallArgs) {
        fire(nextCallArgs);
      }
    }
  
    return function (...args) {
      if (locker) {
        nextCallArgs = args;
      } else {
        fire(args);
      }
    };
  }
  
  /**
   * Wraps a given callback and applies a wait timer, so that minInterval
   * milliseconds must pass since the last call before the callback is actually
   * invoked.
   *
   * @param {Window} win
   * @param {function(...T):R} callback
   * @param {number} minInterval the minimum time interval in millisecond
   * @return {function(...T)}
   * @template T
   * @template R
   */
  export function debounce(win, callback, minInterval) {
    let locker = 0;
    let timestamp = 0;
  
    /** @type {T[]?} */
    let nextCallArgs = null;
  
    /**
     * @param {T[]?} args
     */
    function fire(args) {
      nextCallArgs = null;
      callback.apply(null, args);
    }
  
    /**
     * Wait function for debounce
     */
    function waiter() {
      locker = 0;
      const remaining = minInterval - (Date.now() - timestamp);
      if (remaining > 0) {
        locker = win.setTimeout(waiter, remaining);
      } else {
        fire(nextCallArgs);
      }
    }
  
    return function (...args) {
      timestamp = Date.now();
      nextCallArgs = args;
      if (!locker) {
        locker = win.setTimeout(waiter, minInterval);
      }
    };
  }
  