text: /**
   * @module vow
   * @author Filatov Dmitry <dfilatov@yandex-team.ru>
   * @version 0.4.5
   * @license
   * Dual licensed under the MIT and GPL licenses:
   *   * http://www.opensource.org/licenses/mit-license.php
   *   * http://www.gnu.org/licenses/gpl.html
   */
  
  (function(global) {
  
  /**
   * @class Deferred
   * @exports vow:Deferred
   * @description
   * The `Deferred` class is used to encapsulate newly-created promise object along with functions that resolve, reject or notify it.
   */
  
  /**
   * @constructor
   * @description
   * You can use `vow.defer()` instead of using this constructor.
   *
   * `new vow.Deferred()` gives the same result as `vow.defer()`.
   */
  var Deferred = function() {
      this._promise = new Promise();
  };
  
  Deferred.prototype = /** @lends Deferred.prototype */{
      /**
       * Returns corresponding promise.
       *
       * @returns {vow:Promise}
       */
      promise : function() {
          return this._promise;
      },
  
      /**
       * Resolves corresponding promise with given `value`.
       *
       * @param {*} value
       *
       * @example
       * ```js
       * var defer = vow.defer(),
       *     promise = defer.promise();
       *
       * promise.then(function(value) {
       *     // value is "'success'" here
       * });
       *
       * defer.resolve('success');
       * ```
       */
      resolve : function(value) {
          this._promise.isResolved() || this._promise._resolve(value);
      },
  
      /**
       * Rejects corresponding promise with given `reason`.
       *
       * @param {*} reason
       *
       * @example
       * ```js
       * var defer = vow.defer(),
       *     promise = defer.promise();
       *
       * promise.fail(function(reason) {
       *     // reason is "'something is wrong'" here
       * });
       *
       * defer.reject('something is wrong');
       * ```
       */
      reject : function(reason) {
          this._promise.isResolved() || this._promise._reject(reason);
      },
  
      /**
       * Notifies corresponding promise with given `value`.
       *
       * @param {*} value
       *
       * @example
       * ```js
       * var defer = vow.defer(),
       *     promise = defer.promise();
       *
       * promise.progress(function(value) {
       *     // value is "'20%'", "'40%'" here
       * });
       *
       * defer.notify('20%');
       * defer.notify('40%');
       * ```
       */
      notify : function(value) {
          this._promise.isResolved() || this._promise._notify(value);
      }
  };
  
  var PROMISE_STATUS = {
      PENDING   : 0,
      RESOLVED  : 1,
      FULFILLED : 2,
      REJECTED  : 3
  };
  
  /**
   * @class Promise
   * @exports vow:Promise
   * @description
   * The `Promise` class is used when you want to give to the caller something to subscribe to,
   * but not the ability to resolve or reject the deferred.
   */
  
  /**
   * @constructor
   * @param {Function} resolver See https://github.com/domenic/promises-unwrapping/blob/master/README.md#the-promise-constructor for details.
   * @description
   * You should use this constructor directly only if you are going to use `vow` as DOM Promises implementation.
   * In other case you should use `vow.defer()` and `defer.promise()` methods.
   * @example
   * ```js
   * function fetchJSON(url) {
   *     return new vow.Promise(function(resolve, reject, notify) {
   *         var xhr = new XMLHttpRequest();
   *         xhr.open('GET', url);
   *         xhr.responseType = 'json';
   *         xhr.send();
   *         xhr.onload = function() {
   *             if(xhr.response) {
   *                 resolve(xhr.response);
   *             }
   *             else {
   *                 reject(new TypeError());
   *             }
   *         };
   *     });
   * }
   * ```
   */
  var Promise = function(resolver) {
      this._value = undef;
      this._status = PROMISE_STATUS.PENDING;
  
      this._fulfilledCallbacks = [];
      this._rejectedCallbacks = [];
      this._progressCallbacks = [];
  
      if(resolver) { // NOTE: see https://github.com/domenic/promises-unwrapping/blob/master/README.md
          var _this = this,
              resolverFnLen = resolver.length;
  
          resolver(
              function(val) {
                  _this.isResolved() || _this._resolve(val);
              },
              resolverFnLen > 1?
                  function(reason) {
                      _this.isResolved() || _this._reject(reason);
                  } :
                  undef,
              resolverFnLen > 2?
                  function(val) {
                      _this.isResolved() || _this._notify(val);
                  } :
                  undef);
      }
  };
  
  Promise.prototype = /** @lends Promise.prototype */ {
      /**
       * Returns value of fulfilled promise or reason in case of rejection.
       *
       * @returns {*}
       */
      valueOf : function() {
          return this._value;
      },
  
      /**
       * Returns `true` if promise is resolved.
       *
       * @returns {Boolean}
       */
      isResolved : function() {
          return this._status !== PROMISE_STATUS.PENDING;
      },
  
      /**
       * Returns `true` if promise is fulfilled.
       *
       * @returns {Boolean}
       */
      isFulfilled : function() {
          return this._status === PROMISE_STATUS.FULFILLED;
      },
  
      /**
       * Returns `true` if promise is rejected.
       *
       * @returns {Boolean}
       */
      isRejected : function() {
          return this._status === PROMISE_STATUS.REJECTED;
      },
  
      /**
       * Adds reactions to promise.
       *
       * @param {Function} [onFulfilled] Callback that will to be invoked with the value after promise has been fulfilled
       * @param {Function} [onRejected] Callback that will to be invoked with the reason after promise has been rejected
       * @param {Function} [onProgress] Callback that will to be invoked with the value after promise has been notified
       * @param {Object} [ctx] Context of callbacks execution
       * @returns {vow:Promise} A new promise, see https://github.com/promises-aplus/promises-spec for details
       */
      then : function(onFulfilled, onRejected, onProgress, ctx) {
          var defer = new Deferred();
          this._addCallbacks(defer, onFulfilled, onRejected, onProgress, ctx);
          return defer.promise();
      },
  
      /**
       * Adds rejection reaction only. It is shortcut for `promise.then(undefined, onRejected)`.
       *
       * @param {Function} onRejected Callback to be called with the value after promise has been rejected
       * @param {Object} [ctx] Context of callback execution
       * @returns {vow:Promise}
       */
      'catch' : function(onRejected, ctx) {
          return this.then(undef, onRejected, ctx);
      },
  
      /**
       * Adds rejection reaction only. It is shortcut for `promise.then(null, onRejected)`. It's alias for `catch`.
       *
       * @param {Function} onRejected Callback to be called with the value after promise has been rejected
       * @param {Object} [ctx] Context of callback execution
       * @returns {vow:Promise}
       */
      fail : function(onRejected, ctx) {
          return this.then(undef, onRejected, ctx);
      },
  
      /**
       * Adds resolving reaction (to fulfillment and rejection both).
       *
       * @param {Function} onResolved Callback that to be called with the value after promise has been rejected
       * @param {Object} [ctx] Context of callback execution
       * @returns {vow:Promise}
       */
      always : function(onResolved, ctx) {
          var _this = this,
              cb = function() {
                  return onResolved.call(this, _this);
              };
  
          return this.then(cb, cb, ctx);
      },
  
      /**
       * Adds progress reaction.
       *
       * @param {Function} onProgress Callback to be called with the value when promise has been notified
       * @param {Object} [ctx] Context of callback execution
       * @returns {vow:Promise}
       */
      progress : function(onProgress, ctx) {
          return this.then(undef, undef, onProgress, ctx);
      },
  
      /**
       * Like `promise.then`, but "spreads" the array into a variadic value handler.
       * It is useful with `vow.all` and `vow.allResolved` methods.
       *
       * @param {Function} [onFulfilled] Callback that will to be invoked with the value after promise has been fulfilled
       * @param {Function} [onRejected] Callback that will to be invoked with the reason after promise has been rejected
       * @param {Object} [ctx] Context of callbacks execution
       * @returns {vow:Promise}
       *
       * @example
       * ```js
       * var defer1 = vow.defer(),
       *     defer2 = vow.defer();
       *
       * vow.all([defer1.promise(), defer2.promise()]).spread(function(arg1, arg2) {
       *     // arg1 is "1", arg2 is "'two'" here
       * });
       *
       * defer1.resolve(1);
       * defer2.resolve('two');
       * ```
       */
      spread : function(onFulfilled, onRejected, ctx) {
          return this.then(
              function(val) {
                  return onFulfilled.apply(this, val);
              },
              onRejected,
              ctx);
      },
  
      /**
       * Like `then`, but terminates a chain of promises.
       * If the promise has been rejected, throws it as an exception in a future turn of the event loop.
       *
       * @param {Function} [onFulfilled] Callback that will to be invoked with the value after promise has been fulfilled
       * @param {Function} [onRejected] Callback that will to be invoked with the reason after promise has been rejected
       * @param {Function} [onProgress] Callback that will to be invoked with the value after promise has been notified
       * @param {Object} [ctx] Context of callbacks execution
       *
       * @example
       * ```js
       * var defer = vow.defer();
       * defer.reject(Error('Internal error'));
       * defer.promise().done(); // exception to be thrown
       * ```
       */
      done : function(onFulfilled, onRejected, onProgress, ctx) {
          this
              .then(onFulfilled, onRejected, onProgress, ctx)
              .fail(throwException);
      },
  
      /**
       * Returns a new promise that will be fulfilled in `delay` milliseconds if the promise is fulfilled,
       * or immediately rejected if promise is rejected.
       *
       * @param {Number} delay
       * @returns {vow:Promise}
       */
      delay : function(delay) {
          var timer,
              promise = this.then(function(val) {
                  var defer = new Deferred();
                  timer = setTimeout(
                      function() {
                          defer.resolve(val);
                      },
                      delay);
  
                  return defer.promise();
              });
  
          promise.always(function() {
              clearTimeout(timer);
          });
  
          return promise;
      },
  
      /**
       * Returns a new promise that will be rejected in `timeout` milliseconds
       * if the promise is not resolved beforehand.
       *
       * @param {Number} timeout
       * @returns {vow:Promise}
       *
       * @example
       * ```js
       * var defer = vow.defer(),
       *     promiseWithTimeout1 = defer.promise().timeout(50),
       *     promiseWithTimeout2 = defer.promise().timeout(200);
       *
       * setTimeout(
       *     function() {
       *         defer.resolve('ok');
       *     },
       *     100);
       *
       * promiseWithTimeout1.fail(function(reason) {
       *     // promiseWithTimeout to be rejected in 50ms
       * });
       *
       * promiseWithTimeout2.then(function(value) {
       *     // promiseWithTimeout to be fulfilled with "'ok'" value
       * });
       * ```
       */
      timeout : function(timeout) {
          var defer = new Deferred(),
              timer = setTimeout(
                  function() {
                      defer.reject(Error('timed out'));
                  },
                  timeout);
  
          this.then(
              function(val) {
                  defer.resolve(val);
              },
              function(reason) {
                  defer.reject(reason);
              });
  
          defer.promise().always(function() {
              clearTimeout(timer);
          });
  
          return defer.promise();
      },
  
      _vow : true,
  
      _resolve : function(val) {
          if(this._status > PROMISE_STATUS.RESOLVED) {
              return;
          }
  
          if(val === this) {
              this._reject(TypeError('Can\'t resolve promise with itself'));
              return;
          }
  
          this._status = PROMISE_STATUS.RESOLVED;
  
          if(val && !!val._vow) { // shortpath for vow.Promise
              val.isFulfilled()?
                  this._fulfill(val.valueOf()) :
                  val.isRejected()?
                      this._reject(val.valueOf()) :
                      val.then(
                          this._fulfill,
                          this._reject,
                          this._notify,
                          this);
              return;
          }
  
          if(isObject(val) || isFunction(val)) {
              var then;
              try {
                  then = val.then;
              }
              catch(e) {
                  this._reject(e);
                  return;
              }
  
              if(isFunction(then)) {
                  var _this = this,
                      isResolved = false;
  
                  try {
                      then.call(
                          val,
                          function(val) {
                              if(isResolved) {
                                  return;
                              }
  
                              isResolved = true;
                              _this._resolve(val);
                          },
                          function(err) {
                              if(isResolved) {
                                  return;
                              }
  
                              isResolved = true;
                              _this._reject(err);
                          },
                          function(val) {
                              _this._notify(val);
                          });
                  }
                  catch(e) {
                      isResolved || this._reject(e);
                  }
  
                  return;
              }
          }
  
          this._fulfill(val);
      },
  
      _fulfill : function(val) {
          if(this._status > PROMISE_STATUS.RESOLVED) {
              return;
          }
  
          this._status = PROMISE_STATUS.FULFILLED;
          this._value = val;
  
          this._callCallbacks(this._fulfilledCallbacks, val);
          this._fulfilledCallbacks = this._rejectedCallbacks = this._progressCallbacks = undef;
      },
  
      _reject : function(reason) {
          if(this._status > PROMISE_STATUS.RESOLVED) {
              return;
          }
  
          this._status = PROMISE_STATUS.REJECTED;
          this._value = reason;
  
          this._callCallbacks(this._rejectedCallbacks, reason);
          this._fulfilledCallbacks = this._rejectedCallbacks = this._progressCallbacks = undef;
      },
  
      _notify : function(val) {
          this._callCallbacks(this._progressCallbacks, val);
      },
  
      _addCallbacks : function(defer, onFulfilled, onRejected, onProgress, ctx) {
          if(onRejected && !isFunction(onRejected)) {
              ctx = onRejected;
              onRejected = undef;
          }
          else if(onProgress && !isFunction(onProgress)) {
              ctx = onProgress;
              onProgress = undef;
          }
  
          var cb;
  
          if(!this.isRejected()) {
              cb = { defer : defer, fn : isFunction(onFulfilled)? onFulfilled : undef, ctx : ctx };
              this.isFulfilled()?
                  this._callCallbacks([cb], this._value) :
                  this._fulfilledCallbacks.push(cb);
          }
  
          if(!this.isFulfilled()) {
              cb = { defer : defer, fn : onRejected, ctx : ctx };
              this.isRejected()?
                  this._callCallbacks([cb], this._value) :
                  this._rejectedCallbacks.push(cb);
          }
  
          if(this._status <= PROMISE_STATUS.RESOLVED) {
              this._progressCallbacks.push({ defer : defer, fn : onProgress, ctx : ctx });
          }
      },
  
      _callCallbacks : function(callbacks, arg) {
          var len = callbacks.length;
          if(!len) {
              return;
          }
  
          var isResolved = this.isResolved(),
              isFulfilled = this.isFulfilled();
  
          nextTick(function() {
              var i = 0, cb, defer, fn;
              while(i < len) {
                  cb = callbacks[i++];
                  defer = cb.defer;
                  fn = cb.fn;
  
                  if(fn) {
                      var ctx = cb.ctx,
                          res;
                      try {
                          res = ctx? fn.call(ctx, arg) : fn(arg);
                      }
                      catch(e) {
                          defer.reject(e);
                          continue;
                      }
  
                      isResolved?
                          defer.resolve(res) :
                          defer.notify(res);
                  }
                  else {
                      isResolved?
                          isFulfilled?
                              defer.resolve(arg) :
                              defer.reject(arg) :
                          defer.notify(arg);
                  }
              }
          });
      }
  };
  
  /** @lends Promise */
  var staticMethods = {
      /**
       * Coerces given `value` to a promise, or returns the `value` if it's already a promise.
       *
       * @param {*} value
       * @returns {vow:Promise}
       */
      cast : function(value) {
          return vow.cast(value);
      },
  
      /**
       * Returns a promise to be fulfilled only after all the items in `iterable` are fulfilled,
       * or to be rejected when any of the `iterable` is rejected.
       *
       * @param {Array|Object} iterable
       * @returns {vow:Promise}
       */
      all : function(iterable) {
          return vow.all(iterable);
      },
  
      /**
       * Returns a promise to be fulfilled only when any of the items in `iterable` are fulfilled,
       * or to be rejected when the first item is rejected.
       *
       * @param {Array} iterable
       * @returns {vow:Promise}
       */
      race : function(iterable) {
          return vow.anyResolved(iterable);
      },
  
      /**
       * Returns a promise that has already been resolved with the given `value`.
       * If `value` is a promise, returned promise will be adopted with the state of given promise.
       *
       * @param {*} value
       * @returns {vow:Promise}
       */
      resolve : function(value) {
          return vow.resolve(value);
      },
  
      /**
       * Returns a promise that has already been rejected with the given `reason`.
       *
       * @param {*} reason
       * @returns {vow:Promise}
       */
      reject : function(reason) {
          return vow.reject(reason);
      }
  };
  
  for(var prop in staticMethods) {
      staticMethods.hasOwnProperty(prop) &&
          (Promise[prop] = staticMethods[prop]);
  }
  
  var vow = /** @exports vow */ {
      Deferred : Deferred,
  
      Promise : Promise,
  
      /**
       * Creates a new deferred. This method is a factory method for `vow:Deferred` class.
       * It's equivalent to `new vow.Deferred()`.
       *
       * @returns {vow:Deferred}
       */
      defer : function() {
          return new Deferred();
      },
  
      /**
       * Static equivalent to `promise.then`.
       * If given `value` is not a promise, then `value` is equivalent to fulfilled promise.
       *
       * @param {*} value
       * @param {Function} [onFulfilled] Callback that will to be invoked with the value after promise has been fulfilled
       * @param {Function} [onRejected] Callback that will to be invoked with the reason after promise has been rejected
       * @param {Function} [onProgress] Callback that will to be invoked with the value after promise has been notified
       * @param {Object} [ctx] Context of callbacks execution
       * @returns {vow:Promise}
       */
      when : function(value, onFulfilled, onRejected, onProgress, ctx) {
          return vow.cast(value).then(onFulfilled, onRejected, onProgress, ctx);
      },
  
      /**
       * Static equivalent to `promise.fail`.
       * If given `value` is not a promise, then `value` is equivalent to fulfilled promise.
       *
       * @param {*} value
       * @param {Function} onRejected Callback that will to be invoked with the reason after promise has been rejected
       * @param {Object} [ctx] Context of callback execution
       * @returns {vow:Promise}
       */
      fail : function(value, onRejected, ctx) {
          return vow.when(value, undef, onRejected, ctx);
      },
  
      /**
       * Static equivalent to `promise.always`.
       * If given `value` is not a promise, then `value` is equivalent to fulfilled promise.
       *
       * @param {*} value
       * @param {Function} onResolved Callback that will to be invoked with the reason after promise has been resolved
       * @param {Object} [ctx] Context of callback execution
       * @returns {vow:Promise}
       */
      always : function(value, onResolved, ctx) {
          return vow.when(value).always(onResolved, ctx);
      },
  
      /**
       * Static equivalent to `promise.progress`.
       * If given `value` is not a promise, then `value` is equivalent to fulfilled promise.
       *
       * @param {*} value
       * @param {Function} onProgress Callback that will to be invoked with the reason after promise has been notified
       * @param {Object} [ctx] Context of callback execution
       * @returns {vow:Promise}
       */
      progress : function(value, onProgress, ctx) {
          return vow.when(value).progress(onProgress, ctx);
      },
  
      /**
       * Static equivalent to `promise.spread`.
       * If given `value` is not a promise, then `value` is equivalent to fulfilled promise.
       *
       * @param {*} value
       * @param {Function} [onFulfilled] Callback that will to be invoked with the value after promise has been fulfilled
       * @param {Function} [onRejected] Callback that will to be invoked with the reason after promise has been rejected
       * @param {Object} [ctx] Context of callbacks execution
       * @returns {vow:Promise}
       */
      spread : function(value, onFulfilled, onRejected, ctx) {
          return vow.when(value).spread(onFulfilled, onRejected, ctx);
      },
  
      /**
       * Static equivalent to `promise.done`.
       * If given `value` is not a promise, then `value` is equivalent to fulfilled promise.
       *
       * @param {*} value
       * @param {Function} [onFulfilled] Callback that will to be invoked with the value after promise has been fulfilled
       * @param {Function} [onRejected] Callback that will to be invoked with the reason after promise has been rejected
       * @param {Function} [onProgress] Callback that will to be invoked with the value after promise has been notified
       * @param {Object} [ctx] Context of callbacks execution
       */
      done : function(value, onFulfilled, onRejected, onProgress, ctx) {
          vow.when(value).done(onFulfilled, onRejected, onProgress, ctx);
      },
  
      /**
       * Checks whether the given `value` is a promise-like object
       *
       * @param {*} value
       * @returns {Boolean}
       *
       * @example
       * ```js
       * vow.isPromise('something'); // returns false
       * vow.isPromise(vow.defer().promise()); // returns true
       * vow.isPromise({ then : function() { }); // returns true
       * ```
       */
      isPromise : function(value) {
          return isObject(value) && isFunction(value.then);
      },
  
      /**
       * Coerces given `value` to a promise, or returns the `value` if it's already a promise.
       *
       * @param {*} value
       * @returns {vow:Promise}
       */
      cast : function(value) {
          return vow.isPromise(value)?
              value :
              vow.resolve(value);
      },
  
      /**
       * Static equivalent to `promise.valueOf`.
       * If given `value` is not an instance of `vow.Promise`, then `value` is equivalent to fulfilled promise.
       *
       * @param {*} value
       * @returns {*}
       */
      valueOf : function(value) {
          return value && isFunction(value.valueOf)? value.valueOf() : value;
      },
  
      /**
       * Static equivalent to `promise.isFulfilled`.
       * If given `value` is not an instance of `vow.Promise`, then `value` is equivalent to fulfilled promise.
       *
       * @param {*} value
       * @returns {Boolean}
       */
      isFulfilled : function(value) {
          return value && isFunction(value.isFulfilled)? value.isFulfilled() : true;
      },
  
      /**
       * Static equivalent to `promise.isRejected`.
       * If given `value` is not an instance of `vow.Promise`, then `value` is equivalent to fulfilled promise.
       *
       * @param {*} value
       * @returns {Boolean}
       */
      isRejected : function(value) {
          return value && isFunction(value.isRejected)? value.isRejected() : false;
      },
  
      /**
       * Static equivalent to `promise.isResolved`.
       * If given `value` is not a promise, then `value` is equivalent to fulfilled promise.
       *
       * @param {*} value
       * @returns {Boolean}
       */
      isResolved : function(value) {
          return value && isFunction(value.isResolved)? value.isResolved() : true;
      },
  
      /**
       * Returns a promise that has already been resolved with the given `value`.
       * If `value` is a promise, returned promise will be adopted with the state of given promise.
       *
       * @param {*} value
       * @returns {vow:Promise}
       */
      resolve : function(value) {
          var res = vow.defer();
          res.resolve(value);
          return res.promise();
      },
  
      /**
       * Returns a promise that has already been fulfilled with the given `value`.
       * If `value` is a promise, returned promise will be fulfilled with fulfill/rejection value of given promise.
       *
       * @param {*} value
       * @returns {vow:Promise}
       */
      fulfill : function(value) {
          var defer = vow.defer(),
              promise = defer.promise();
  
          defer.resolve(value);
  
          return promise.isFulfilled()?
              promise :
              promise.then(null, function(reason) {
                  return reason;
              });
      },
  
      /**
       * Returns a promise that has already been rejected with the given `reason`.
       * If `reason` is a promise, returned promise will be rejected with fulfill/rejection value of given promise.
       *
       * @param {*} reason
       * @returns {vow:Promise}
       */
      reject : function(reason) {
          if(vow.isPromise(reason)) {
              return reason.then(function(val) {
                  var defer = vow.defer();
                  defer.reject(val);
                  return defer.promise();
              });
          }
  
          var defer = vow.defer();
          defer.reject(reason);
          return defer.promise();
      },
  
      /**
       * Invokes a given function `fn` with arguments `args`
       *
       * @param {Function} fn
       * @param {...*} [args]
       * @returns {vow:Promise}
       *
       * @example
       * ```js
       * var promise1 = vow.invoke(function(value) {
       *         return value;
       *     }, 'ok'),
       *     promise2 = vow.invoke(function() {
       *         throw Error();
       *     });
       *
       * promise1.isFulfilled(); // true
       * promise1.valueOf(); // 'ok'
       * promise2.isRejected(); // true
       * promise2.valueOf(); // instance of Error
       * ```
       */
      invoke : function(fn, args) {
          var len = Math.max(arguments.length - 1, 0),
              callArgs;
          if(len) { // optimization for V8
              callArgs = Array(len);
              var i = 0;
              while(i < len) {
                  callArgs[i++] = arguments[i];
              }
          }
  
          try {
              return vow.resolve(callArgs?
                  fn.apply(global, callArgs) :
                  fn.call(global));
          }
          catch(e) {
              return vow.reject(e);
          }
      },
  
      /**
       * Returns a promise to be fulfilled only after all the items in `iterable` are fulfilled,
       * or to be rejected when any of the `iterable` is rejected.
       *
       * @param {Array|Object} iterable
       * @returns {vow:Promise}
       *
       * @example
       * with array:
       * ```js
       * var defer1 = vow.defer(),
       *     defer2 = vow.defer();
       *
       * vow.all([defer1.promise(), defer2.promise(), 3])
       *     .then(function(value) {
       *          // value is "[1, 2, 3]" here
       *     });
       *
       * defer1.resolve(1);
       * defer2.resolve(2);
       * ```
       *
       * @example
       * with object:
       * ```js
       * var defer1 = vow.defer(),
       *     defer2 = vow.defer();
       *
       * vow.all({ p1 : defer1.promise(), p2 : defer2.promise(), p3 : 3 })
       *     .then(function(value) {
       *          // value is "{ p1 : 1, p2 : 2, p3 : 3 }" here
       *     });
       *
       * defer1.resolve(1);
       * defer2.resolve(2);
       * ```
       */
      all : function(iterable) {
          var defer = new Deferred(),
              isPromisesArray = isArray(iterable),
              keys = isPromisesArray?
                  getArrayKeys(iterable) :
                  getObjectKeys(iterable),
              len = keys.length,
              res = isPromisesArray? [] : {};
  
          if(!len) {
              defer.resolve(res);
              return defer.promise();
          }
  
          var i = len;
          vow._forEach(
              iterable,
              function() {
                  if(!--i) {
                      var j = 0;
                      while(j < len) {
                          res[keys[j]] = vow.valueOf(iterable[keys[j++]]);
                      }
                      defer.resolve(res);
                  }
              },
              defer.reject,
              defer.notify,
              defer,
              keys);
  
          return defer.promise();
      },
  
      /**
       * Returns a promise to be fulfilled only after all the items in `iterable` are resolved.
       *
       * @param {Array|Object} iterable
       * @returns {vow:Promise}
       *
       * @example
       * ```js
       * var defer1 = vow.defer(),
       *     defer2 = vow.defer();
       *
       * vow.allResolved([defer1.promise(), defer2.promise()]).spread(function(promise1, promise2) {
       *     promise1.isRejected(); // returns true
       *     promise1.valueOf(); // returns "'error'"
       *     promise2.isFulfilled(); // returns true
       *     promise2.valueOf(); // returns "'ok'"
       * });
       *
       * defer1.reject('error');
       * defer2.resolve('ok');
       * ```
       */
      allResolved : function(iterable) {
          var defer = new Deferred(),
              isPromisesArray = isArray(iterable),
              keys = isPromisesArray?
                  getArrayKeys(iterable) :
                  getObjectKeys(iterable),
              i = keys.length,
              res = isPromisesArray? [] : {};
  
          if(!i) {
              defer.resolve(res);
              return defer.promise();
          }
  
          var onResolved = function() {
                  --i || defer.resolve(iterable);
              };
  
          vow._forEach(
              iterable,
              onResolved,
              onResolved,
              defer.notify,
              defer,
              keys);
  
          return defer.promise();
      },
  
      allPatiently : function(iterable) {
          return vow.allResolved(iterable).then(function() {
              var isPromisesArray = isArray(iterable),
                  keys = isPromisesArray?
                      getArrayKeys(iterable) :
                      getObjectKeys(iterable),
                  rejectedPromises, fulfilledPromises,
                  len = keys.length, i = 0, key, promise;
  
              if(!len) {
                  return isPromisesArray? [] : {};
              }
  
              while(i < len) {
                  key = keys[i++];
                  promise = iterable[key];
                  if(vow.isRejected(promise)) {
                      rejectedPromises || (rejectedPromises = isPromisesArray? [] : {});
                      isPromisesArray?
                          rejectedPromises.push(promise.valueOf()) :
                          rejectedPromises[key] = promise.valueOf();
                  }
                  else if(!rejectedPromises) {
                      (fulfilledPromises || (fulfilledPromises = isPromisesArray? [] : {}))[key] = vow.valueOf(promise);
                  }
              }
  
              if(rejectedPromises) {
                  throw rejectedPromises;
              }
  
              return fulfilledPromises;
          });
      },
  
      /**
       * Returns a promise to be fulfilled only when any of the items in `iterable` are fulfilled,
       * or to be rejected when all the items are rejected (with the reason of the first rejected item).
       *
       * @param {Array} iterable
       * @returns {vow:Promise}
       */
      any : function(iterable) {
          var defer = new Deferred(),
              len = iterable.length;
  
          if(!len) {
              defer.reject(Error());
              return defer.promise();
          }
  
          var i = 0, reason;
          vow._forEach(
              iterable,
              defer.resolve,
              function(e) {
                  i || (reason = e);
                  ++i === len && defer.reject(reason);
              },
              defer.notify,
              defer);
  
          return defer.promise();
      },
  
      /**
       * Returns a promise to be fulfilled only when any of the items in `iterable` are fulfilled,
       * or to be rejected when the first item is rejected.
       *
       * @param {Array} iterable
       * @returns {vow:Promise}
       */
      anyResolved : function(iterable) {
          var defer = new Deferred(),
              len = iterable.length;
  
          if(!len) {
              defer.reject(Error());
              return defer.promise();
          }
  
          vow._forEach(
              iterable,
              defer.resolve,
              defer.reject,
              defer.notify,
              defer);
  
          return defer.promise();
      },
  
      /**
       * Static equivalent to `promise.delay`.
       * If given `value` is not a promise, then `value` is equivalent to fulfilled promise.
       *
       * @param {*} value
       * @param {Number} delay
       * @returns {vow:Promise}
       */
      delay : function(value, delay) {
          return vow.resolve(value).delay(delay);
      },
  
      /**
       * Static equivalent to `promise.timeout`.
       * If given `value` is not a promise, then `value` is equivalent to fulfilled promise.
       *
       * @param {*} value
       * @param {Number} timeout
       * @returns {vow:Promise}
       */
      timeout : function(value, timeout) {
          return vow.resolve(value).timeout(timeout);
      },
  
      _forEach : function(promises, onFulfilled, onRejected, onProgress, ctx, keys) {
          var len = keys? keys.length : promises.length,
              i = 0;
          while(i < len) {
              vow.when(promises[keys? keys[i] : i], onFulfilled, onRejected, onProgress, ctx);
              ++i;
          }
      }
  };
  
  var undef,
      nextTick = (function() {
          var fns = [],
              enqueueFn = function(fn) {
                  return fns.push(fn) === 1;
              },
              callFns = function() {
                  var fnsToCall = fns, i = 0, len = fns.length;
                  fns = [];
                  while(i < len) {
                      fnsToCall[i++]();
                  }
              };
  
          if(typeof setImmediate === 'function') { // ie10, nodejs >= 0.10
              return function(fn) {
                  enqueueFn(fn) && setImmediate(callFns);
              };
          }
  
          if(typeof process === 'object' && process.nextTick) { // nodejs < 0.10
              return function(fn) {
                  enqueueFn(fn) && process.nextTick(callFns);
              };
          }
  
          if(global.postMessage) { // modern browsers
              var isPostMessageAsync = true;
              if(global.attachEvent) {
                  var checkAsync = function() {
                          isPostMessageAsync = false;
                      };
                  global.attachEvent('onmessage', checkAsync);
                  global.postMessage('__checkAsync', '*');
                  global.detachEvent('onmessage', checkAsync);
              }
  
              if(isPostMessageAsync) {
                  var msg = '__promise' + +new Date,
                      onMessage = function(e) {
                          if(e.data === msg) {
                              e.stopPropagation && e.stopPropagation();
                              callFns();
                          }
                      };
  
                  global.addEventListener?
                      global.addEventListener('message', onMessage, true) :
                      global.attachEvent('onmessage', onMessage);
  
                  return function(fn) {
                      enqueueFn(fn) && global.postMessage(msg, '*');
                  };
              }
          }
  
          var doc = global.document;
          if('onreadystatechange' in doc.createElement('script')) { // ie6-ie8
              var createScript = function() {
                      var script = doc.createElement('script');
                      script.onreadystatechange = function() {
                          script.parentNode.removeChild(script);
                          script = script.onreadystatechange = null;
                          callFns();
                  };
                  (doc.documentElement || doc.body).appendChild(script);
              };
  
              return function(fn) {
                  enqueueFn(fn) && createScript();
              };
          }
  
          return function(fn) { // old browsers
              enqueueFn(fn) && setTimeout(callFns, 0);
          };
      })(),
      throwException = function(e) {
          nextTick(function() {
              throw e;
          });
      },
      isFunction = function(obj) {
          return typeof obj === 'function';
      },
      isObject = function(obj) {
          return obj !== null && typeof obj === 'object';
      },
      toStr = Object.prototype.toString,
      isArray = Array.isArray || function(obj) {
          return toStr.call(obj) === '[object Array]';
      },
      getArrayKeys = function(arr) {
          var res = [],
              i = 0, len = arr.length;
          while(i < len) {
              res.push(i++);
          }
          return res;
      },
      getObjectKeys = Object.keys || function(obj) {
          var res = [];
          for(var i in obj) {
              obj.hasOwnProperty(i) && res.push(i);
          }
          return res;
      };
  
  var defineAsGlobal = true;
  if(typeof exports === 'object') {
      module.exports = vow;
      defineAsGlobal = false;
  }
  
  if(typeof modules === 'object') {
      modules.define('vow', function(provide) {
          provide(vow);
      });
      defineAsGlobal = false;
  }
  
  if(typeof define === 'function') {
      define(function(require, exports, module) {
          module.exports = vow;
      });
      defineAsGlobal = false;
  }
  
  defineAsGlobal && (global.vow = vow);
  
  })(this);
  (function (global) {
  
      /**
       * API error.
       *
       * @param {String} type Error type.
       * @param {String} message Human-readable description of the error.
       */
      function ApiError(type, message) {
          this.name = 'ApiError';
          this.type = type;
          this.message = message;
  
          if (Error.captureStackTrace) {
              Error.captureStackTrace(this, this.constructor);
          }
      }
  
      ApiError.prototype = Object.create(Error.prototype, {
          constructor: {
              value: ApiError
          }
      });
  
      /**
       * Invalid or missed parameter.
       */
      ApiError.BAD_REQUEST = 'BAD_REQUEST';
  
      /**
       * Unspecified error or server logic error.
       */
      ApiError.INTERNAL_ERROR = 'INTERNAL_ERROR';
  
      /**
       * API middleware wasn't found.
       */
      ApiError.NOT_FOUND = 'NOT_FOUND';
  
      var defineAsGlobal = true;
  
      /**
       * @see https://github.com/ymaps/modules
       */
      if (typeof global.modules === 'object') {
          global.modules.define('bla-error', function (provide) {
              provide(ApiError);
          });
          defineAsGlobal = false;
      }
  
      /**
       * @see requirejs.org
       */
      if (typeof global.define === 'function') {
          global.define('bla-error', function () {
              return ApiError;
          });
          defineAsGlobal = false;
      }
  
      if (defineAsGlobal) {
          global.bla = global.bla || {};
          global.bla.ApiError = ApiError;
      }
  
  }(this));
  (function (global) {
      /**
       * Borrowed from `next-tick` by Dmitry Filatov.
       * @see https://github.com/bem/bem-core/blob/v2/common.blocks/next-tick/next-tick.vanilla.js
       *
       * Calls a callback function in the next tick.
       *
       * @param {Function} callback
       */
      var nextTick = (function () {
          var fns = [];
          var enqueueFn = function (fn) {
              return fns.push(fn) === 1;
          };
          var callFns = function () {
              var fnsToCall = fns;
              var i = 0;
              var len = fns.length;
              fns = [];
              while (i < len) {
                  fnsToCall[i++]();
              }
          };
  
          if (global.postMessage && global.addEventListener) { // modern browsers
              var msg = '__nextTick' + Date.now();
              var onMessage = function (e) {
                  if (e.data === msg) {
                      e.stopPropagation && e.stopPropagation();
                      callFns();
                  }
              };
              global.addEventListener('message', onMessage, true);
              return function (fn) {
                  enqueueFn(fn) && global.postMessage(msg, '*');
              };
          }
  
          return function (fn) { // old browsers
              enqueueFn(fn) && setTimeout(callFns, 0);
          };
      })();
  
      /**
       * Returns API class based on dependecies.
       *
       * @param {Object} vow
       * @param {Object} ApiError
       * @returns {Function}
       */
      function createApiClass(vow, ApiError) {
  
          /**
           * Makes an ajax request.
           *
           * @param {String} url A string containing the URL to which the request is sent.
           * @param {String} data Data to be sent to the server.
           * @returns {vow.Promise}
           */
          function sendAjaxRequest(url, data) {
              var xhr = new XMLHttpRequest();
              var d = vow.defer();
  
              xhr.onreadystatechange = function () {
                  if (xhr.readyState === XMLHttpRequest.DONE) {
                      if (xhr.status === 200) {
                          d.resolve(JSON.parse(xhr.responseText));
                      } else {
                          d.reject(xhr);
                      }
                  }
              };
  
              xhr.open('POST', url, true);
              xhr.setRequestHeader('Accept', 'application/json, text/javascript, */*; q=0.01');
              xhr.setRequestHeader('Content-type', 'application/json');
              xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
              xhr.send(data);
  
              return d.promise();
          }
  
          /*
           * Set of predefined API errors.
           */
          var XHR_ERRORS = {
              404: new ApiError(ApiError.NOT_FOUND, 'API middleware wasn\'t found')
          };
  
          /**
           * Api provider.
           *
           * @param {String} basePath Url path to the middleware root.
           * @param {Object} [options] Extra options.
           * @param {Boolean} [options.disableBatch=false] Disable using batch mode.
           */
          function Api(basePath, options) {
              this._basePath = basePath;
              this._options = {
                  disableBatch: options && options.disableBatch
              };
              this._batch = [];
              this._deferreds = {};
          }
  
          Api.prototype = {
              constructor: Api,
  
              /**
               * Executes api by path with specified parameters.
               *
               * @param {String} methodName Method name.
               * @param {Object} params Data should be sent to the method.
               * @returns {vow.Promise}
               */
              exec: function (methodName, params) {
                  return this._options.disableBatch ?
                      this._execWithoutBatching(methodName, params) :
                      this._execWithBatching(methodName, params);
              },
  
              /**
               * Executes method immediately.
               *
               * @param {String} methodName Method name.
               * @param {Object} params Data should be sent to the method.
               * @returns {vow.Promise}
               */
              _execWithoutBatching: function (methodName, params) {
                  var defer = vow.defer();
                  var url = this._basePath + methodName;
                  var data = JSON.stringify(params);
  
                  sendAjaxRequest(url, data).then(
                      this._resolvePromise.bind(this, defer),
                      this._rejectPromise.bind(this, defer)
                  );
  
                  return defer.promise();
              },
  
              /**
               * Executes method with a little delay, adding it to batch.
               *
               * @param {String} methodName Method name.
               * @param {Object} params Data should be sent to the method.
               * @returns {vow.Promise}
               */
              _execWithBatching: function (methodName, params) {
                  var requestId = this._getRequestId(methodName, params);
                  var promise = this._getRequestPromise(requestId);
  
                  if (!promise) {
                      this._addToBatch(methodName, params);
                      promise = this._createPromise(requestId);
                      this._run();
                  }
  
                  return promise;
              },
  
              /**
               * Generates an ID for a method request.
               *
               * @param {String} methodName
               * @param {Object} params
               * @returns {String}
               */
              _getRequestId: function (methodName, params) {
                  var stringifiedParams = JSON.stringify(params) || '';
                  return methodName + stringifiedParams;
              },
  
              /**
               * Gets the promise object for given request ID.
               *
               * @param {String} requestId Request ID for which promise is retrieved.
               * @returns {vow.Promise|undefined}
               */
              _getRequestPromise: function (requestId) {
                  var defer = this._deferreds[requestId];
                  return defer && defer.promise();
              },
  
              /**
               * Appends data to the batch array.
               *
               * @param {String} methodName
               * @param {Object} params
               */
              _addToBatch: function (methodName, params) {
                  this._batch.push({
                      method: methodName,
                      params: params
                  });
              },
  
              /**
               * Creates new deferred promise.
               *
               * @param {String} requestId Request ID for which promise is generated.
               * @returns {vow.Promise}
               */
              _createPromise: function (requestId) {
                  var defer = vow.defer();
                  this._deferreds[requestId] = defer;
                  return defer.promise();
              },
  
              /**
               * Initializes async batch request.
               */
              _run: function () {
                  // The collecting requests for the batch will start when a first request is received.
                  // That's why the batch length is checked there.
                  if (this._batch.length === 1) {
                      nextTick(this._sendBatchRequest.bind(this));
                  }
              },
  
              /**
               * Performs batch request.
               */
              _sendBatchRequest: function () {
                  var url = this._basePath + 'bla-batch';
                  var data = JSON.stringify({methods: this._batch});
                  sendAjaxRequest(url, data).then(
                      this._resolvePromises.bind(this, this._batch),
                      this._rejectPromises.bind(this, this._batch)
                  );
  
                  this._batch = [];
              },
  
              /**
               * Resolve deferred promise.
               *
               * @param {vow.Deferred} defer
               * @param {Object} response Server response.
               */
              _resolvePromise: function (defer, response) {
                  var error = response.error;
                  if (error) {
                      defer.reject(new ApiError(error.type, error.message));
                  } else {
                      defer.resolve(response.data);
                  }
              },
  
              /**
               * Resolves deferred promises.
               *
               * @param {Object[]} batch Batch request data.
               * @param {Object} response Server response.
               */
              _resolvePromises: function (batch, response) {
                  var data = response.data;
                  for (var i = 0, requestId; i < batch.length; i++) {
                      requestId = this._getRequestId(batch[i].method, batch[i].params);
                      this._resolvePromise(this._deferreds[requestId], data[i]);
                      delete this._deferreds[requestId];
                  }
              },
  
              /**
               * Rejects deferred promise.
               *
               * @param {vow.Deferred} defer
               * @param {XMLHttpRequest} xhr
               */
              _rejectPromise: function (defer, xhr) {
                  var errorMessage = xhr.message || xhr.statusText || xhr.responseText;
                  var error = XHR_ERRORS[xhr.status] || new ApiError(ApiError.INTERNAL_ERROR, errorMessage);
                  defer.reject(error);
              },
  
              /**
               * Rejects deferred promises.
               *
               * @param {Object[]} batch Batch request data.
               * @param {XMLHttpRequest} xhr
               */
              _rejectPromises: function (batch, xhr) {
                  for (var i = 0, requestId; i < batch.length; i++) {
                      requestId = this._getRequestId(batch[i].method, batch[i].params);
                      this._rejectPromise(this._deferreds[requestId], xhr);
                      delete this._deferreds[requestId];
                  }
              }
          };
  
          return Api;
      }
  
      var defineAsGlobal = true;
  
      /**
       * @see https://github.com/ymaps/modules
       */
      if (typeof global.modules === 'object') {
          global.modules.define('bla', ['vow', 'bla-error'], function (provide, vow, ApiError) {
              var Api = createApiClass(vow, ApiError);
              provide(Api);
          });
          defineAsGlobal = false;
      }
  
      /**
       * @see requirejs.org
       */
      if (typeof global.define === 'function') {
          global.define('bla', ['bla-error', 'vow'], function (ApiError, vow) {
              return createApiClass(vow, ApiError);
          });
          defineAsGlobal = false;
      }
  
      if (defineAsGlobal) {
          global.bla = global.bla || {};
          global.bla.Api = createApiClass(global.vow, global.bla.ApiError);
      }
  
  }(this));
  