Source: base.js

function BaseFactory() {
  /*jshint strict:false */
  var initializing = false,
      // Need to check which version of function.toString we have
      superPattern = /xy/.test(function () {return 'xy';}) ? /\b_super\b/ : /.*/;

  function executeQueue(idx, data) {
    var self = this,
        i = 0;
    for(; i < self.$$cbQueue.length; i++) {
      if (self.$$cbQueue[i].idx <= idx) {
        if (
          (self.$$cbQueue[i].type < 3 && self.$$finals[idx] && self.$$finals[idx].resolved === true) || // Success (type=1) & Always (type=2)
          (self.$$cbQueue[i].type > 1 && self.$$cbQueue[i].type < 4 && self.$$finals[idx] && self.$$finals[idx].rejected === true) || // Fail (type=3) & Always (type=2)
          (self.$$cbQueue[i].type === 4 && (!self.$$finals[idx] || (!self.$$finals[idx].resolved && !self.$$finals[idx].rejected))) // Progress (type=4)
        ) {
          self.$$cbQueue[i].cb.call(self, data);
        }
        // If this thread is resolved or rejected, then remove the cb from the queue to keep executions faster
        if (self.$$finals[idx].resolved || self.$$finals[idx].rejected) {
          self.$$cbQueue.splice(i, 1);
          i--;
        }
      }
    }
  }

  /**
  Event that triggers when the source model is cloned
  @event Base#cloned
  @prop {Base} clone - The new instance that was created as a result of the clone
  */
  /**
  Event that triggers when the model is resolved (generally when data is loaded succesfully)
  @event Base#resolved
  */
  /**
  Event that triggers when the model is rejected (generally when a data load fails)
  @event Base#rejected
  */
  /**
  Event that triggers when the model is notified (progress is recorded)
  @event Base#notified
  */
  /**
  Event that triggers when the model is finalized (resolved or rejected)
  @event Base#finalized
  */
  /**
  Event that triggers when the model is unfinalized (reset back to being neither resolved nor rejected)
  @event Base#unfinalized
  */

	/**
  Base model that all other models will inherit from. Provides Promises/A functionality as well as publish/subscribe functionality.
  @constructs Base
  @prop {Object}  $errors       - Contains details about any error states on the instance
  */
  function Base() {}

  Base.prototype = {
    $type: 'Base',
    /**
    Initialization method. Called automatically when a new instance is instantiated.
    @param           [data]            - Initial data to populate the instance with
    @param {Boolean} [forClone=false]  - Whether this instance is being created as a clone or not
    @return {Base} `this`
    */
    init: function ( data, forClone) {
      /*jshint unused:false */
      var self = this;
      self.$$arguments = m_copy(arguments);
      self.$$cbQueue = [];
      self.$$cbQueueIdx = 1;
      self.$$finals = [];
      self.$$listeners = {};
      self.$errors = {};
      return self;
    },
    /**
    Method to clone the instance.
    @return {Base} `new this.constructor(null, true)`
    @fires Base#cloned
    */
    clone: function () {
      var self = this,
          ret = new self.constructor(null, true);
      ret.$$arguments = m_copy(self.$$arguments);
      self.trigger('cloned', ret);
      return ret;
    },
    /**
    Indicates whether the instance has been finalized (resolved or rejected)
    @arg {number} [idx=this.$$cbQueueIdx] Thread index to check
    @return {Boolean}
    */
    isFinal: function (idx) {
      var self = this;
      idx = idx || self.$$cbQueueIdx;
      if (self.$$finals[idx]) {
        return !!(self.$$finals[idx].resolved || self.$$finals[idx].rejected);
      }
      return false;
    },
    /**
    Marks the promie thread as "resolved" (successfully complete).
    @arg [idx=this.$$cbQueueIdx] - Promise thread to resolve
    @arg [data] - Data related to the resolution
    @fires Base#resolved
    @fires Base#finalized
    @return {Base} `this`
    */
    resolve: function (idx, data) {
      var self = this;
      idx = idx || self.$$cbQueueIdx;
      if (!self.isFinal(idx)) {
        self.$$finals[idx] = {
          resolved: true,
          data: data
        };
        executeQueue.call(self, idx, data);
        self.trigger('resolved', data);
      }
      return self;
    },
    /**
    Marks the promise thread as "rejected" (unsuccessfully complete).
    @arg [idx=this.$$cbQueueIdx] - Promise thread to reject
    @arg [data] - Data related to the rejection
    @fires Base#rejected
    @fires Base#finalized
    @returns {Base} `this`
    */
    reject: function (idx, data) {
      var self = this;
      idx = idx || self.$$cbQueueIdx;
      if (!self.isFinal(idx)) {
        self.$$finals[idx] = {
          rejected: true,
          data: data
        };
        executeQueue.call(self, idx, data);
        self.trigger('rejected', data);
      }
      return self;
    },
    /**
    Triggers a progress step for the provided promise thread.
    @arg [idx=this.$$cbQueueIdx] - Promise thread to notify of progress
    @arg [data] - Data related to the progress step
    @fires Base#notified
    @returns {Base} `this`
    */
    notify: function (idx, data) {
      var self = this;
      idx = idx || self.$$cbQueueIdx;
      if (!self.isFinal(idx)) {
        executeQueue.call(self, idx, data);
        self.trigger('notified', data);
      }
      return self;
    },
    /**
    "Resets" the Promise state on the instance by incrementing the current promise thread index.
    @fires Base#unfinalized
    @returns {number} `idx` New promise thread index
    */
    unfinalize: function () {
      this.trigger('unfinalized');
      return ++this.$$cbQueueIdx;
    },
    /**
    Attaches success/fail/progress callbacks to the current promise thread, which will trigger upon the next resolve/reject call respectively or, if the current promise thread is already final, immediately.
    @arg {Base~successCallback}   [success]
    @arg {Base~failCallback}      [fail]
    @arg {Base~progressCallback}  [progress]
    @returns {Base} `this`
    */
    /**
    Success callback will be triggered when/if the current promise thread is resolved.
    @callback Base~successCallback
    */
    /**
    Fail callback will be triggered when/if the current promise thread is rejected.
    @callback Base~failCallback
    */
    /**
    Progress callback will be triggered as the current promise thread passes through various states of progress.
    @callback Base~progressCallback
    */
    then: function(success, fail, progress) {
      var self = this;
      if (m_isFunction(success)) {
        self.$$cbQueue.push({
          type: 1,
          cb: success,
          idx: self.$$cbQueueIdx
        });
      }
      if (m_isFunction(fail)) {
        self.$$cbQueue.push({
          type: 3,
          cb: fail,
          idx: self.$$cbQueueIdx
        });
      }
      if (m_isFunction(progress)) {
        self.$$cbQueue.push({
          type: 4,
          cb: progress,
          idx: self.$$cbQueueIdx
        });
      }
      if (self.$$finals[self.$$cbQueueIdx]) {
        executeQueue.call(self, self.$$cbQueueIdx, self.$$finals[self.$$cbQueueIdx].data);
      }
      return self;
    },
    /**
    Attaches a callback to the current promise thread which will trigger upon the next finalization or, if the current promise thread is already final, immediately.
    @arg {Base~alwaysCallback} [always]
    @returns {Base} `this`
    */
    /**
    Always callback will be triggered when/if the current promise thread is finalized (either resolved OR rejected).
    @callback Base~alwaysCallback
    */
    always: function (always) {
      var self = this;
      if (m_isFunction(always)) {
        self.$$cbQueue.push({
          type: 2,
          cb: always,
          idx: self.$$cbQueueIdx
        });
      }
      if (self.$$finals[self.$$cbQueueIdx]) {
        executeQueue.call(self, self.$$cbQueueIdx, self.$$finals[self.$$cbQueueIdx].data);
      }
      return self;
    },
    /**
    Attaches success callback to the current promise thread.
    @param {Base~successCallback} [success]
    @return {Base} `this`
    */
    success: function (cb) {
      return this.then(cb);
    },
    /**
    Attaches fail callback to the current promise thread.
    @param {Base~failCallback} [fail]
    @return {Base} `this`
    */
    fail: function (cb) {
      return this.then(null, cb);
    },
    /**
    Attaches a progress callback to the current promise thread.
    @param {Base~progressCallback} [progress]
    @return {Base} `this`
    */
    progress: function (cb) {
      return this.then(null, null, cb);
    },
    /**
    Attaches a listener to an event type.
    @param {String} type - The type of event to listen for
    @param {Function} cb - The function to trigger every time the event type occurs
    @return {Base} `this`
    */
    bind: function (type, cb) {
      var self = this;
      if (m_isString(type) && m_isFunction(cb)) {
        self.$$listeners[type] = self.$$listeners[type] || [];
        self.$$listeners[type].push(cb);
      }
      return self;
    },
    /**
    Detaches either all listeners or just a single listener from an event type.
    @param {String} type - The type of event to unbind
    @param {Function} [listener] - The specific listener to unbind from the event type. If not provided, all listeners bound to the event type will be removed
    @return {Base} `this`
    */
    unbind: function (type, listener) {
      var self = this,
          idx;
      if (m_isString(type) && m_isArray(self.$$listeners[type]) && self.$$listeners[type].length > 0) {
        if (m_isFunction(listener)) {
          self.$$listeners[type] = filter(self.$$listeners[type], function (cb) {
            return cb !== listener;
          });
        } else {
          delete self.$$listeners[type];
        }
      }
      return self;
    },
    /**
    Attaches a one-time listener to an event type. After triggering once, the listener will automtically be unbound.
    @param {String} type - The type of event to listen for
    @param {Function} cb - The function to trigger the next time the event type occurs
    @return {Base} `this`
    */
    one: function (type, cb) {
      var self = this,
          wrap;
      if (m_isString(type) && m_isFunction(cb)) {
        wrap = function () {
          cb.call(this, arguments);
          self.unbind(type, wrap);
        };
        self.bind(type, wrap);
      }
      return self;
    },
    /**
    Triggers an event of the given type, passing any listeners the data provided.
    @param {String} type    - The type of event to trigger
    @param          [data]  - Object to pass into any listeners
    @return {Boolean} Returns `true` if all listeners return true, else `false`
    */
    trigger: function (type, data) {
      var self = this,
          ret = true;
      if (m_isString(type) && m_isArray(self.$$listeners[type]) && self.$$listeners[type].length > 0) {
        m_forEach(self.$$listeners[type], function (cb) {
          ret = cb.call(self, data, type) && ret;
        });
      }
      return ret;
    }
  };

  /**
  Allows for model extension
  @param {Object} properties - Properties to extend the new model with. Methods may call `this._super.apply(this, arguments)` to call parent model methods that are overwritten.
  @extends Base
  @return {Function} New constructor
  */
  Base.extend = function extend(properties) {
    var _super = this.prototype,
        proto, key;

    function construct(constructor, args) {
      function Class() {
        return constructor.apply(this, args);
      }
      Class.prototype = constructor.prototype;
      return new Class();
    }

    function createFnProp (key, fn, super2) {
      return function() {
        var tmp = this._super,
            ret;

        this._super = super2[ key ];
        ret = fn.apply(this, arguments);
        if (m_isFunction(tmp)) {
          this._super = tmp;
        } else {
          delete this._super;
        }
        return ret;
      };
    }
    
    function Class() {
      if (this.constructor !== Class) {
        return construct(Class, arguments);
      }
      if (!initializing && m_isFunction(this.init)) {
        return this.init.apply(this, arguments);
      }
    }

    initializing = true;
    proto = new this();
    initializing = false;
    if (!properties.$type) {
      properties.$type = 'Class';
    }

    if (m_isFunction(proto.$preExtend)) {
      properties = proto.$preExtend(properties);
    }
    for (key in properties) {
      if (properties.hasOwnProperty(key)) {
        if (m_isFunction(properties[ key ]) && m_isFunction(_super[ key ]) && superPattern.test(properties[ key ])) {
          proto[ key ] = createFnProp(key, properties[ key ], _super);
        } else {
          proto[ key ] = properties[ key ];
        }
      }
    }
    Class.prototype = proto;
    if (Object.defineProperty) {
      Object.defineProperty( Class.prototype, 'constructor', {
        enumerable: false,
        value: Class
      });
    } else {
      Class.prototype.constructor = Class;
    }
    Class.extend = extend;
    return Class;
  };
 
  /**
   * Return the constructor function
   */
  return Base;
}

angular.module( 'angular-m' )
  .factory( 'Base', BaseFactory );