commands/cancellable.js

/**
 * A Cancellation Token (ct) that commands can check for cancellation.
 * Commands should regularly check for the
 * {@link commands.Cancellable#isCancelled|isCancelled}
 * attribute and exit eagerly if true.
 * Keeps a list of timeout IDs issued by
 * {@link https://developer.mozilla.org/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout|setTimeout}
 * calls and cancels them all when {@link commands.Cancellable#cancel|cancel} is called,
 * setting the {@link commands.Cancellable#isCancelled|isCancelled} attribute to true.
 * @memberof commands
 */
class Cancellable {

  /** Cancellable constructor. */
  constructor() {
    /** If the Cancellation Token is cancelled. Starts off as false. */
    this.isCancelled = false;
    // Object storing timeout IDs and related Promise resolve functions.
    this._timeoutIDs = {};
  }

  /**
   * Registers the given timeout ID and
   * {@link https://developer.mozilla.org/docs/Web/JavaScript/Guide/Using_promises|Promise}
   * resolve function.
   * @package
   * @param timeoutID - Timeout ID, the result of calling
   *   {@link https://developer.mozilla.org/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout|setTimeout},
   *   platform dependent.
   * @param {function} resolve - Resolve function for a Promise to be called
   *   if the timeout is cancelled.
   */
  add(timeoutID, resolve) {
    this._timeoutIDs[timeoutID.id||timeoutID] = [timeoutID, resolve];
  }

  /**
   * Unregisters the given timeout ID, when the timeout is reached and
   * does not need to be cancelled anymore, or if it was cancelled.
   * @package
   * @param timeoutID - Timeout ID to unregister.
   */
  del(timeoutID) {
    delete this._timeoutIDs[timeoutID.id||timeoutID];
  }

  /**
   * Cancels all registered timeouts. Sets {@link commands.Cancellable#isCancelled|isCancelled} to true.
   * Cancellation means calling {@link https://developer.mozilla.org/docs/Web/API/WindowOrWorkerGlobalScope/clearTimeout|clearTimeout}
   * with the stored timeout IDs and calling the related resolve functions.
   */
  cancel() {
    this.isCancelled = true;
    for (let [, [timeoutID, resolve]] of Object.entries(this._timeoutIDs)) {
      this.del(timeoutID);
      clearTimeout(timeoutID);
      resolve();
    }
  }

}

module.exports = {Cancellable};