Source: lib/util/cancelable_chain.js

/**
 * @license
 * Copyright 2016 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

goog.provide('shaka.util.CancelableChain');

goog.require('goog.asserts');
goog.require('shaka.util.Error');



/**
 * A Promise-based abstraction that creates cancelable Promise chains.
 * When canceled, subsequent stages of the internal Promise chain will stop.
 * A canceled chain is rejected with a user-specified value.
 *
 * A CancelableChain only supports linear Promise chains.  Chains which branch
 * (more than one then() handler chained to a particular stage) are not
 * supported.  You will not be prevented from treating this as if branching
 * were supported, but everything will be serialized into a linear chain.
 * Be careful!
 *
 * @constructor
 * @struct
 */
shaka.util.CancelableChain = function() {
  /** @private {!Promise} */
  this.promise_ = Promise.resolve();

  /** @private {boolean} */
  this.final_ = false;

  /** @private {boolean} */
  this.complete_ = false;

  /** @private {boolean} */
  this.canceled_ = false;

  /** @private {shaka.util.Error} */
  this.rejectionValue_;

  /** @private {function()} */
  this.onCancelComplete_;

  /** @private {!Promise} */
  this.cancelPromise_ = new Promise(function(resolve) {
    this.onCancelComplete_ = resolve;
  }.bind(this));

  /**
   * Callbacks for when the chain completes successfully.
   * @private {!Array.<!function()>}
   */
  this.onComplete_ = [];

  /**
   * Callbacks for when the chain is cancelled.
   * @private {!Array.<!function()>}
   */
  this.onCancel_ = [];
};


/**
 * @param {function(*)} callback
 * @return {!shaka.util.CancelableChain} the chain itself.
 */
shaka.util.CancelableChain.prototype.then = function(callback) {
  goog.asserts.assert(!this.final_, 'Chain should not be final!');

  this.promise_ = this.promise_.then(callback).then(function(data) {
    if (this.canceled_) {
      this.onCancelComplete_();
      return Promise.reject(this.rejectionValue_);
    }
    return Promise.resolve(data);
  }.bind(this));
  return this;
};


/**
 * Finalize the chain.
 * Converts the chain into a simple Promise and stops accepting new stages.
 *
 * @return {!Promise}
 */
shaka.util.CancelableChain.prototype.finalize = function() {
  if (!this.final_) {
    this.promise_ = this.promise_.then(function(data) {
      this.complete_ = true;
      // Tel everyone that is listening that the chain has completed.
      this.onComplete_.forEach(function(callback) { callback(); });
      return Promise.resolve(data);
    }.bind(this), function(error) {
      this.complete_ = true;
      if (this.canceled_) {
        this.onCancelComplete_();
        return Promise.reject(this.rejectionValue_);
      }

      return Promise.reject(error);
    }.bind(this));
  }
  this.final_ = true;
  return this.promise_;
};


/**
 * Cancel the Promise chain and reject with the given value.
 *
 * @param {!shaka.util.Error} reason
 * @return {!Promise} resolved when the cancelation has been processed by the
 *   the chain and no more stages will execute.  Note that this may be before
 *   the owner of the finalized chain has seen the rejection.
 */
shaka.util.CancelableChain.prototype.cancel = function(reason) {
  if (this.complete_) return Promise.resolve();

  this.canceled_ = true;
  this.rejectionValue_ = reason;

  // Tell everyone that is listening that the chain has been cancelled.
  this.onCancel_.forEach(function(callback) { callback(); });

  return this.cancelPromise_;
};


/**
 * Add a callback to be called if the chain is canceled mid-execution. Calls to
 * |onCancel| are accumulative.
 *
 * @param {!function()} callback
 */
shaka.util.CancelableChain.prototype.onCancel = function(callback) {
  this.onCancel_.push(callback);
};


/**
 * Add a callback to be called if the chain is completed. Calls to
 * |onComplete| are accumulative.
 *
 * @param {!function()} callback
 */
shaka.util.CancelableChain.prototype.onComplete = function(callback) {
  this.onComplete_.push(callback);
};