diff --git a/common.blocks/vow/vow.vanilla.js b/common.blocks/vow/vow.vanilla.js index 5eae9eec6..8a091e7b6 100644 --- a/common.blocks/vow/vow.vanilla.js +++ b/common.blocks/vow/vow.vanilla.js @@ -1,7 +1,7 @@ /** * @module vow * @author Filatov Dmitry - * @version 0.4.13 + * @version 0.4.17 * @license * Dual licensed under the MIT and GPL licenses: * * http://www.opensource.org/licenses/mit-license.php @@ -10,1323 +10,1364 @@ (function(global) { -var undef, - nextTick = (function() { - var fns = [], - enqueueFn = function(fn) { - fns.push(fn); - return fns.length === 1; - }, - callFns = function() { - var fnsToCall = fns, i = 0, len = fns.length; - fns = []; - while(i < len) { - fnsToCall[i++](); - } - }; + var undef, + nextTick = (function() { + var fns = [], + enqueueFn = function(fn) { + fns.push(fn); + return fns.length === 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 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(typeof process === 'object' && process.nextTick) { // nodejs < 0.10 + return function(fn) { + enqueueFn(fn) && process.nextTick(callFns); + }; + } - var MutationObserver = global.MutationObserver || global.WebKitMutationObserver; // modern browsers - if(MutationObserver) { - var num = 1, - node = document.createTextNode(''); + var MutationObserver = global.MutationObserver || global.WebKitMutationObserver; // modern browsers + if(MutationObserver) { + var num = 1, + node = document.createTextNode(''); - new MutationObserver(callFns).observe(node, { characterData : true }); + new MutationObserver(callFns).observe(node, { characterData : true }); - return function(fn) { - enqueueFn(fn) && (node.data = (num *= -1)); - }; - } + return function(fn) { + enqueueFn(fn) && (node.data = (num *= -1)); + }; + } + + if(global.postMessage) { + 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' + Math.random() + '_' +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); - if(global.postMessage) { - var isPostMessageAsync = true; - if(global.attachEvent) { - var checkAsync = function() { - isPostMessageAsync = false; + return function(fn) { + enqueueFn(fn) && global.postMessage(msg, '*'); }; - global.attachEvent('onmessage', checkAsync); - global.postMessage('__checkAsync', '*'); - global.detachEvent('onmessage', checkAsync); + } } - if(isPostMessageAsync) { - var msg = '__promise' + Math.random() + '_' +new Date, - onMessage = function(e) { - if(e.data === msg) { - e.stopPropagation && e.stopPropagation(); + 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(); - } }; - - global.addEventListener? - global.addEventListener('message', onMessage, true) : - global.attachEvent('onmessage', onMessage); + (doc.documentElement || doc.body).appendChild(script); + }; return function(fn) { - enqueueFn(fn) && global.postMessage(msg, '*'); + enqueueFn(fn) && createScript(); }; } - } - 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) { // old browsers + enqueueFn(fn) && setTimeout(callFns, 0); }; - - return function(fn) { - enqueueFn(fn) && createScript(); + })(), + 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; + }, + defineCustomErrorType = function(name) { + var res = function(message) { + this.name = name; + this.message = message; }; - } - 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; - }, - defineCustomErrorType = function(name) { - var res = function(message) { - this.name = name; - this.message = message; - }; - - res.prototype = new Error(); - - return res; - }, - wrapOnFulfilled = function(onFulfilled, idx) { - return function(val) { - onFulfilled.call(this, val, idx); - }; - }; + res.prototype = new Error(); -/** - * @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(); -}; + return res; + }, + wrapOnFulfilled = function(onFulfilled, idx) { + return function(val) { + onFulfilled.call(this, val, idx); + }; + }, + emitUnhandledRejection = global.PromiseRejectionEvent? + function(reason, promise) { + new global.PromiseRejectionEvent( + 'unhandledrejection', + { + promise : promise, + reason : reason + }); + } : + typeof process === 'object' && process.emit? + function(reason, promise) { + process.emit('unhandledRejection', reason, promise); + } : + function() {}; -Deferred.prototype = /** @lends Deferred.prototype */{ /** - * Returns the corresponding promise. - * - * @returns {vow:Promise} + * @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. */ - promise : function() { - return this._promise; - }, /** - * Resolves the corresponding promise with the given `value`. - * - * @param {*} value + * @constructor + * @description + * You can use `vow.defer()` instead of using this constructor. * - * @example - * ```js - * var defer = vow.defer(), - * promise = defer.promise(); - * - * promise.then(function(value) { - * // value is "'success'" here - * }); - * - * defer.resolve('success'); - * ``` + * `new vow.Deferred()` gives the same result as `vow.defer()`. */ - resolve : function(value) { - this._promise.isResolved() || this._promise._resolve(value); - }, + var Deferred = function() { + this._promise = new Promise(); + }; - /** - * Rejects the corresponding promise with the 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) { - if(this._promise.isResolved()) { - return; - } + Deferred.prototype = /** @lends Deferred.prototype */{ + /** + * Returns the corresponding promise. + * + * @returns {vow:Promise} + */ + promise : function() { + return this._promise; + }, + + /** + * Resolves the corresponding promise with the 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 the corresponding promise with the 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) { + if(this._promise.isResolved()) { + return; + } - if(vow.isPromise(reason)) { - reason = reason.then(function(val) { - var defer = vow.defer(); - defer.reject(val); - return defer.promise(); - }); - this._promise._resolve(reason); - } - else { - this._promise._reject(reason); + if(vow.isPromise(reason)) { + reason = reason.then(function(val) { + var defer = vow.defer(); + defer.reject(val); + return defer.promise(); + }); + this._promise._resolve(reason); + } + else { + this._promise._reject(reason); + } + }, + + /** + * Notifies the corresponding promise with the 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); } - }, - - /** - * Notifies the corresponding promise with the 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 the value of the fulfilled promise or the reason in case of rejection. - * - * @returns {*} - */ - valueOf : function() { - return this._value; - }, - - /** - * Returns `true` if the promise is resolved. - * - * @returns {Boolean} - */ - isResolved : function() { - return this._status !== PROMISE_STATUS.PENDING; - }, + }; - /** - * Returns `true` if the promise is fulfilled. - * - * @returns {Boolean} - */ - isFulfilled : function() { - return this._status === PROMISE_STATUS.FULFILLED; - }, + var PROMISE_STATUS = { + PENDING : 0, + RESOLVED : 1, + FULFILLED : 2, + REJECTED : 3 + }; /** - * Returns `true` if the promise is rejected. - * - * @returns {Boolean} + * @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. */ - isRejected : function() { - return this._status === PROMISE_STATUS.REJECTED; - }, /** - * Adds reactions to the promise. - * - * @param {Function} [onFulfilled] Callback that will be invoked with a provided value after the promise has been fulfilled - * @param {Function} [onRejected] Callback that will be invoked with a provided reason after the promise has been rejected - * @param {Function} [onProgress] Callback that will be invoked with a provided value after the promise has been notified - * @param {Object} [ctx] Context of the callbacks execution - * @returns {vow:Promise} A new promise, see https://github.com/promises-aplus/promises-spec for details + * @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()); + * } + * }; + * }); + * } + * ``` */ - then : function(onFulfilled, onRejected, onProgress, ctx) { - var defer = new Deferred(); - this._addCallbacks(defer, onFulfilled, onRejected, onProgress, ctx); - return defer.promise(); - }, + var Promise = function(resolver) { + this._value = undef; + this._status = PROMISE_STATUS.PENDING; + this._shouldEmitUnhandledRejection = true; - /** - * Adds only a rejection reaction. This method is a shorthand for `promise.then(undefined, onRejected)`. - * - * @param {Function} onRejected Callback that will be called with a provided 'reason' as argument after the promise has been rejected - * @param {Object} [ctx] Context of the callback execution - * @returns {vow:Promise} - */ - 'catch' : function(onRejected, ctx) { - return this.then(undef, onRejected, ctx); - }, + this._fulfilledCallbacks = []; + this._rejectedCallbacks = []; + this._progressCallbacks = []; - /** - * Adds only a rejection reaction. This method is a shorthand for `promise.then(null, onRejected)`. It's also an alias for `catch`. - * - * @param {Function} onRejected Callback to be called with the value after promise has been rejected - * @param {Object} [ctx] Context of the callback execution - * @returns {vow:Promise} - */ - fail : function(onRejected, ctx) { - return this.then(undef, onRejected, ctx); - }, + if(resolver) { // NOTE: see https://github.com/domenic/promises-unwrapping/blob/master/README.md + var _this = this, + resolverFnLen = resolver.length; - /** - * Adds a resolving reaction (for both fulfillment and rejection). - * - * @param {Function} onResolved Callback that will be invoked with the promise as an argument, after the promise has been resolved. - * @param {Object} [ctx] Context of the callback execution - * @returns {vow:Promise} - */ - always : function(onResolved, ctx) { - var _this = this, - cb = function() { - return onResolved.call(this, _this); - }; + try { + 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); + } + catch(e) { + this._reject(e); + } + } + }; - return this.then(cb, cb, ctx); - }, + Promise.prototype = /** @lends Promise.prototype */ { + /** + * Returns the value of the fulfilled promise or the reason in case of rejection. + * + * @returns {*} + */ + valueOf : function() { + return this._value; + }, + + /** + * Returns `true` if the promise is resolved. + * + * @returns {Boolean} + */ + isResolved : function() { + return this._status !== PROMISE_STATUS.PENDING; + }, + + /** + * Returns `true` if the promise is fulfilled. + * + * @returns {Boolean} + */ + isFulfilled : function() { + return this._status === PROMISE_STATUS.FULFILLED; + }, + + /** + * Returns `true` if the promise is rejected. + * + * @returns {Boolean} + */ + isRejected : function() { + return this._status === PROMISE_STATUS.REJECTED; + }, + + /** + * Adds reactions to the promise. + * + * @param {Function} [onFulfilled] Callback that will be invoked with a provided value after the promise has been fulfilled + * @param {Function} [onRejected] Callback that will be invoked with a provided reason after the promise has been rejected + * @param {Function} [onProgress] Callback that will be invoked with a provided value after the promise has been notified + * @param {Object} [ctx] Context of the callbacks execution + * @returns {vow:Promise} A new promise, see https://github.com/promises-aplus/promises-spec for details + */ + then : function(onFulfilled, onRejected, onProgress, ctx) { + this._shouldEmitUnhandledRejection = false; + var defer = new Deferred(); + this._addCallbacks(defer, onFulfilled, onRejected, onProgress, ctx); + return defer.promise(); + }, + + /** + * Adds only a rejection reaction. This method is a shorthand for `promise.then(undefined, onRejected)`. + * + * @param {Function} onRejected Callback that will be called with a provided 'reason' as argument after the promise has been rejected + * @param {Object} [ctx] Context of the callback execution + * @returns {vow:Promise} + */ + 'catch' : function(onRejected, ctx) { + return this.then(undef, onRejected, ctx); + }, + + /** + * Adds only a rejection reaction. This method is a shorthand for `promise.then(null, onRejected)`. It's also an alias for `catch`. + * + * @param {Function} onRejected Callback to be called with the value after promise has been rejected + * @param {Object} [ctx] Context of the callback execution + * @returns {vow:Promise} + */ + fail : function(onRejected, ctx) { + return this.then(undef, onRejected, ctx); + }, + + /** + * Adds a resolving reaction (for both fulfillment and rejection). + * + * @param {Function} onResolved Callback that will be invoked with the promise as an argument, after the promise has been resolved. + * @param {Object} [ctx] Context of the callback execution + * @returns {vow:Promise} + */ + always : function(onResolved, ctx) { + var _this = this, + cb = function() { + return onResolved.call(this, _this); + }; - /** - * Adds a progress reaction. - * - * @param {Function} onProgress Callback that will be called with a provided value when the promise has been notified - * @param {Object} [ctx] Context of the callback execution - * @returns {vow:Promise} - */ - progress : function(onProgress, ctx) { - return this.then(undef, undef, onProgress, ctx); - }, + return this.then(cb, cb, ctx); + }, + + /** + * Adds a progress reaction. + * + * @param {Function} onProgress Callback that will be called with a provided value when the promise has been notified + * @param {Object} [ctx] Context of the 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 the `vow.all` and the `vow.allResolved` methods. + * + * @param {Function} [onFulfilled] Callback that will be invoked with a provided value after the promise has been fulfilled + * @param {Function} [onRejected] Callback that will be invoked with a provided reason after the promise has been rejected + * @param {Object} [ctx] Context of the 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, this method throws it's "reason" as an exception in a future turn of the event loop. + * + * @param {Function} [onFulfilled] Callback that will be invoked with a provided value after the promise has been fulfilled + * @param {Function} [onRejected] Callback that will be invoked with a provided reason after the promise has been rejected + * @param {Function} [onProgress] Callback that will be invoked with a provided value after the promise has been notified + * @param {Object} [ctx] Context of the 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 the 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); - /** - * Like `promise.then`, but "spreads" the array into a variadic value handler. - * It is useful with the `vow.all` and the `vow.allResolved` methods. - * - * @param {Function} [onFulfilled] Callback that will be invoked with a provided value after the promise has been fulfilled - * @param {Function} [onRejected] Callback that will be invoked with a provided reason after the promise has been rejected - * @param {Object} [ctx] Context of the 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); - }, + return defer.promise(); + }); - /** - * Like `then`, but terminates a chain of promises. - * If the promise has been rejected, this method throws it's "reason" as an exception in a future turn of the event loop. - * - * @param {Function} [onFulfilled] Callback that will be invoked with a provided value after the promise has been fulfilled - * @param {Function} [onRejected] Callback that will be invoked with a provided reason after the promise has been rejected - * @param {Function} [onProgress] Callback that will be invoked with a provided value after the promise has been notified - * @param {Object} [ctx] Context of the 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); - }, + promise.always(function() { + clearTimeout(timer); + }); - /** - * Returns a new promise that will be fulfilled in `delay` milliseconds if the promise is fulfilled, - * or immediately rejected if the promise is rejected. - * - * @param {Number} delay - * @returns {vow:Promise} - */ - delay : function(delay) { - var timer, - promise = this.then(function(val) { - var defer = new Deferred(); + 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.resolve(val); + defer.reject(new vow.TimedOutError('timed out')); }, - delay); - - return defer.promise(); - }); - - promise.always(function() { - clearTimeout(timer); - }); + timeout); - 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(new vow.TimedOutError('timed out')); + this.then( + function(val) { + defer.resolve(val); }, - timeout); - - this.then( - function(val) { - defer.resolve(val); - }, - function(reason) { - defer.reject(reason); - }); + function(reason) { + defer.reject(reason); + }); - defer.promise().always(function() { - clearTimeout(timer); - }); + defer.promise().always(function() { + clearTimeout(timer); + }); - return defer.promise(); - }, + return defer.promise(); + }, - _vow : true, + _vow : true, - _resolve : function(val) { - if(this._status > PROMISE_STATUS.RESOLVED) { - return; - } + _resolve : function(val) { + if(this._status > PROMISE_STATUS.RESOLVED) { + return; + } - if(val === this) { - this._reject(TypeError('Can\'t resolve promise with itself')); - return; - } + if(val === this) { + this._reject(TypeError('Can\'t resolve promise with itself')); + return; + } - this._status = PROMISE_STATUS.RESOLVED; + 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()) : + if(val && !!val._vow) { // shortpath for vow.Promise + if(val.isFulfilled()) { + this._fulfill(val.valueOf()); + } + else if(val.isRejected()) { + val._shouldEmitUnhandledRejection = false; + this._reject(val.valueOf()); + } + else { 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; - + if(isObject(val) || isFunction(val)) { + var then; 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); - }); + then = val.then; } catch(e) { - isResolved || this._reject(e); + this._reject(e); + return; } - 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(), - isRejected = this.isRejected(); + if(isFunction(then)) { + var _this = this, + isResolved = false; - 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); + 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) { - defer.reject(e); - continue; + isResolved || this._reject(e); } - isResolved? - defer.resolve(res) : - defer.notify(res); - } - else if(isFulfilled) { - defer.resolve(arg); - } - else if(isRejected) { - defer.reject(arg); - } - else { - defer.notify(arg); + return; } } - }); - } -}; -/** @lends Promise */ -var staticMethods = { - /** - * Coerces the 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, that will be fulfilled only after all the items in `iterable` are fulfilled. - * If any of the `iterable` items gets rejected, then the returned promise will be rejected. - * - * @param {Array|Object} iterable - * @returns {vow:Promise} - */ - all : function(iterable) { - return vow.all(iterable); - }, - - /** - * Returns a promise, that will be fulfilled only when any of the items in `iterable` are fulfilled. - * If any of the `iterable` items gets rejected, then the returned promise will be 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, the returned promise will have `value`'s state. - * - * @param {*} value - * @returns {vow:Promise} - */ - resolve : function(value) { - return vow.resolve(value); - }, + this._fulfill(val); + }, - /** - * Returns a promise that has already been rejected with the given `reason`. - * - * @param {*} reason - * @returns {vow:Promise} - */ - reject : function(reason) { - return vow.reject(reason); - } -}; + _fulfill : function(val) { + if(this._status > PROMISE_STATUS.RESOLVED) { + return; + } -for(var prop in staticMethods) { - staticMethods.hasOwnProperty(prop) && - (Promise[prop] = staticMethods[prop]); -} + this._status = PROMISE_STATUS.FULFILLED; + this._value = val; -var vow = /** @exports vow */ { - Deferred : Deferred, + this._callCallbacks(this._fulfilledCallbacks, val); + this._fulfilledCallbacks = this._rejectedCallbacks = this._progressCallbacks = undef; + }, - Promise : Promise, + _reject : function(reason) { + if(this._status > PROMISE_STATUS.RESOLVED) { + return; + } - /** - * 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(); - }, + this._status = PROMISE_STATUS.REJECTED; + this._value = reason; - /** - * Static equivalent to `promise.then`. - * If `value` is not a promise, then `value` is treated as a fulfilled promise. - * - * @param {*} value - * @param {Function} [onFulfilled] Callback that will be invoked with a provided value after the promise has been fulfilled - * @param {Function} [onRejected] Callback that will be invoked with a provided reason after the promise has been rejected - * @param {Function} [onProgress] Callback that will be invoked with a provided value after the promise has been notified - * @param {Object} [ctx] Context of the callbacks execution - * @returns {vow:Promise} - */ - when : function(value, onFulfilled, onRejected, onProgress, ctx) { - return vow.cast(value).then(onFulfilled, onRejected, onProgress, ctx); - }, + this._callCallbacks(this._rejectedCallbacks, reason); - /** - * Static equivalent to `promise.fail`. - * If `value` is not a promise, then `value` is treated as a fulfilled promise. - * - * @param {*} value - * @param {Function} onRejected Callback that will be invoked with a provided reason after the promise has been rejected - * @param {Object} [ctx] Context of the callback execution - * @returns {vow:Promise} - */ - fail : function(value, onRejected, ctx) { - return vow.when(value, undef, onRejected, ctx); - }, + if(!this._rejectedCallbacks.length) { + var _this = this; + nextTick(function() { + if(_this._shouldEmitUnhandledRejection) { + emitUnhandledRejection(reason, _this); + } + }); + } - /** - * Static equivalent to `promise.always`. - * If `value` is not a promise, then `value` is treated as a fulfilled promise. - * - * @param {*} value - * @param {Function} onResolved Callback that will be invoked with the promise as an argument, after the promise has been resolved. - * @param {Object} [ctx] Context of the callback execution - * @returns {vow:Promise} - */ - always : function(value, onResolved, ctx) { - return vow.when(value).always(onResolved, ctx); - }, + this._fulfilledCallbacks = this._rejectedCallbacks = this._progressCallbacks = undef; + }, - /** - * Static equivalent to `promise.progress`. - * If `value` is not a promise, then `value` is treated as a fulfilled promise. - * - * @param {*} value - * @param {Function} onProgress Callback that will be invoked with a provided value after the promise has been notified - * @param {Object} [ctx] Context of the callback execution - * @returns {vow:Promise} - */ - progress : function(value, onProgress, ctx) { - return vow.when(value).progress(onProgress, ctx); - }, + _notify : function(val) { + this._callCallbacks(this._progressCallbacks, val); + }, - /** - * Static equivalent to `promise.spread`. - * If `value` is not a promise, then `value` is treated as a fulfilled promise. - * - * @param {*} value - * @param {Function} [onFulfilled] Callback that will be invoked with a provided value after the promise has been fulfilled - * @param {Function} [onRejected] Callback that will be invoked with a provided reason after the promise has been rejected - * @param {Object} [ctx] Context of the 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 `value` is not a promise, then `value` is treated as a fulfilled promise. - * - * @param {*} value - * @param {Function} [onFulfilled] Callback that will be invoked with a provided value after the promise has been fulfilled - * @param {Function} [onRejected] Callback that will be invoked with a provided reason after the promise has been rejected - * @param {Function} [onProgress] Callback that will be invoked with a provided value after the promise has been notified - * @param {Object} [ctx] Context of the 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); - }, + _addCallbacks : function(defer, onFulfilled, onRejected, onProgress, ctx) { + if(onRejected && !isFunction(onRejected)) { + ctx = onRejected; + onRejected = undef; + } + else if(onProgress && !isFunction(onProgress)) { + ctx = onProgress; + onProgress = undef; + } - /** - * Coerces the given `value` to a promise, or returns the `value` if it's already a promise. - * - * @param {*} value - * @returns {vow:Promise} - */ - cast : function(value) { - return value && !!value._vow? - value : - vow.resolve(value); - }, + if(onRejected) { + this._shouldEmitUnhandledRejection = false; + } - /** - * Static equivalent to `promise.valueOf`. - * If `value` is not a promise, then `value` is treated as a fulfilled promise. - * - * @param {*} value - * @returns {*} - */ - valueOf : function(value) { - return value && isFunction(value.valueOf)? value.valueOf() : value; - }, + var cb; - /** - * Static equivalent to `promise.isFulfilled`. - * If `value` is not a promise, then `value` is treated as a fulfilled promise. - * - * @param {*} value - * @returns {Boolean} - */ - isFulfilled : function(value) { - return value && isFunction(value.isFulfilled)? value.isFulfilled() : true; - }, + if(!this.isRejected()) { + cb = { defer : defer, fn : isFunction(onFulfilled)? onFulfilled : undef, ctx : ctx }; + this.isFulfilled()? + this._callCallbacks([cb], this._value) : + this._fulfilledCallbacks.push(cb); + } - /** - * Static equivalent to `promise.isRejected`. - * If `value` is not a promise, then `value` is treated as a fulfilled promise. - * - * @param {*} value - * @returns {Boolean} - */ - isRejected : function(value) { - return value && isFunction(value.isRejected)? value.isRejected() : false; - }, + if(!this.isFulfilled()) { + cb = { defer : defer, fn : onRejected, ctx : ctx }; + this.isRejected()? + this._callCallbacks([cb], this._value) : + this._rejectedCallbacks.push(cb); + } - /** - * Static equivalent to `promise.isResolved`. - * If `value` is not a promise, then `value` is treated as a fulfilled promise. - * - * @param {*} value - * @returns {Boolean} - */ - isResolved : function(value) { - return value && isFunction(value.isResolved)? value.isResolved() : true; - }, + if(this._status <= PROMISE_STATUS.RESOLVED) { + this._progressCallbacks.push({ defer : defer, fn : onProgress, ctx : ctx }); + } + }, - /** - * Returns a promise that has already been resolved with the given `value`. - * If `value` is a promise, the returned promise will have `value`'s state. - * - * @param {*} value - * @returns {vow:Promise} - */ - resolve : function(value) { - var res = vow.defer(); - res.resolve(value); - return res.promise(); - }, + _callCallbacks : function(callbacks, arg) { + var len = callbacks.length; + if(!len) { + return; + } - /** - * Returns a promise that has already been fulfilled with the given `value`. - * If `value` is a promise, the returned promise will be fulfilled with the fulfill/rejection value of `value`. - * - * @param {*} value - * @returns {vow:Promise} - */ - fulfill : function(value) { - var defer = vow.defer(), - promise = defer.promise(); + var isResolved = this.isResolved(), + isFulfilled = this.isFulfilled(), + isRejected = this.isRejected(); - defer.resolve(value); + 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; + } - return promise.isFulfilled()? - promise : - promise.then(null, function(reason) { - return reason; + isFulfilled || isRejected? + defer.resolve(res) : + defer.notify(res); + } + else if(isFulfilled) { + defer.resolve(arg); + } + else if(isRejected) { + defer.reject(arg); + } + else { + defer.notify(arg); + } + } }); - }, - - /** - * Returns a promise that has already been rejected with the given `reason`. - * If `reason` is a promise, the returned promise will be rejected with the fulfill/rejection value of `reason`. - * - * @param {*} reason - * @returns {vow:Promise} - */ - reject : function(reason) { - var defer = vow.defer(); - defer.reject(reason); - return defer.promise(); - }, - - /** - * Invokes the 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, that will be fulfilled only after all the items in `iterable` are fulfilled. - * If any of the `iterable` items gets rejected, the promise will be 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(); + /** @lends Promise */ + var staticMethods = { + /** + * Coerces the 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, that will be fulfilled only after all the items in `iterable` are fulfilled. + * If any of the `iterable` items gets rejected, then the returned promise will be rejected. + * + * @param {Array|Object} iterable + * @returns {vow:Promise} + */ + all : function(iterable) { + return vow.all(iterable); + }, + + /** + * Returns a promise, that will be fulfilled only when any of the items in `iterable` are fulfilled. + * If any of the `iterable` items gets rejected, then the returned promise will be 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, the returned promise will have `value`'s state. + * + * @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); } + }; - var i = len; - vow._forEach( - iterable, - function(value, idx) { - res[keys[idx]] = value; - if(!--i) { - defer.resolve(res); - } - }, - defer.reject, - defer.notify, - defer, - keys); - - return defer.promise(); - }, + for(var prop in staticMethods) { + staticMethods.hasOwnProperty(prop) && + (Promise[prop] = staticMethods[prop]); + } - /** - * Returns a promise, that will 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); + 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 `value` is not a promise, then `value` is treated as a fulfilled promise. + * + * @param {*} value + * @param {Function} [onFulfilled] Callback that will be invoked with a provided value after the promise has been fulfilled + * @param {Function} [onRejected] Callback that will be invoked with a provided reason after the promise has been rejected + * @param {Function} [onProgress] Callback that will be invoked with a provided value after the promise has been notified + * @param {Object} [ctx] Context of the 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 `value` is not a promise, then `value` is treated as a fulfilled promise. + * + * @param {*} value + * @param {Function} onRejected Callback that will be invoked with a provided reason after the promise has been rejected + * @param {Object} [ctx] Context of the callback execution + * @returns {vow:Promise} + */ + fail : function(value, onRejected, ctx) { + return vow.when(value, undef, onRejected, ctx); + }, + + /** + * Static equivalent to `promise.always`. + * If `value` is not a promise, then `value` is treated as a fulfilled promise. + * + * @param {*} value + * @param {Function} onResolved Callback that will be invoked with the promise as an argument, after the promise has been resolved. + * @param {Object} [ctx] Context of the callback execution + * @returns {vow:Promise} + */ + always : function(value, onResolved, ctx) { + return vow.when(value).always(onResolved, ctx); + }, + + /** + * Static equivalent to `promise.progress`. + * If `value` is not a promise, then `value` is treated as a fulfilled promise. + * + * @param {*} value + * @param {Function} onProgress Callback that will be invoked with a provided value after the promise has been notified + * @param {Object} [ctx] Context of the callback execution + * @returns {vow:Promise} + */ + progress : function(value, onProgress, ctx) { + return vow.when(value).progress(onProgress, ctx); + }, + + /** + * Static equivalent to `promise.spread`. + * If `value` is not a promise, then `value` is treated as a fulfilled promise. + * + * @param {*} value + * @param {Function} [onFulfilled] Callback that will be invoked with a provided value after the promise has been fulfilled + * @param {Function} [onRejected] Callback that will be invoked with a provided reason after the promise has been rejected + * @param {Object} [ctx] Context of the 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 `value` is not a promise, then `value` is treated as a fulfilled promise. + * + * @param {*} value + * @param {Function} [onFulfilled] Callback that will be invoked with a provided value after the promise has been fulfilled + * @param {Function} [onRejected] Callback that will be invoked with a provided reason after the promise has been rejected + * @param {Function} [onProgress] Callback that will be invoked with a provided value after the promise has been notified + * @param {Object} [ctx] Context of the 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 the given `value` to a promise, or returns the `value` if it's already a promise. + * + * @param {*} value + * @returns {vow:Promise} + */ + cast : function(value) { + return value && !!value._vow? + value : + vow.resolve(value); + }, + + /** + * Static equivalent to `promise.valueOf`. + * If `value` is not a promise, then `value` is treated as a fulfilled promise. + * + * @param {*} value + * @returns {*} + */ + valueOf : function(value) { + return value && isFunction(value.valueOf)? value.valueOf() : value; + }, + + /** + * Static equivalent to `promise.isFulfilled`. + * If `value` is not a promise, then `value` is treated as a fulfilled promise. + * + * @param {*} value + * @returns {Boolean} + */ + isFulfilled : function(value) { + return value && isFunction(value.isFulfilled)? value.isFulfilled() : true; + }, + + /** + * Static equivalent to `promise.isRejected`. + * If `value` is not a promise, then `value` is treated as a fulfilled promise. + * + * @param {*} value + * @returns {Boolean} + */ + isRejected : function(value) { + return value && isFunction(value.isRejected)? value.isRejected() : false; + }, + + /** + * Static equivalent to `promise.isResolved`. + * If `value` is not a promise, then `value` is treated as a 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, the returned promise will have `value`'s state. + * + * @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, the returned promise will be fulfilled with the fulfill/rejection value of `value`. + * + * @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, the returned promise will be rejected with the fulfill/rejection value of `reason`. + * + * @param {*} reason + * @returns {vow:Promise} + */ + reject : function(reason) { + var defer = vow.defer(); + defer.reject(reason); return defer.promise(); - } + }, + + /** + * Invokes the 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]; + } + } - var onResolved = function() { - --i || defer.resolve(iterable); - }; + try { + return vow.resolve(callArgs? + fn.apply(global, callArgs) : + fn.call(global)); + } + catch(e) { + return vow.reject(e); + } + }, + + /** + * Returns a promise, that will be fulfilled only after all the items in `iterable` are fulfilled. + * If any of the `iterable` items gets rejected, the promise will be 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? [] : {}; - vow._forEach( - iterable, - onResolved, - onResolved, - defer.notify, - defer, - keys); + if(!len) { + defer.resolve(res); + return defer.promise(); + } - return defer.promise(); - }, + var i = len; + vow._forEach( + iterable, + function(value, idx) { + res[keys[idx]] = value; + if(!--i) { + defer.resolve(res); + } + }, + defer.reject, + defer.notify, + defer, + keys); - allPatiently : function(iterable) { - return vow.allResolved(iterable).then(function() { - var isPromisesArray = isArray(iterable), + return defer.promise(); + }, + + /** + * Returns a promise, that will 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), - rejectedPromises, fulfilledPromises, - len = keys.length, i = 0, key, promise; + i = keys.length, + res = isPromisesArray? [] : {}; - if(!len) { - return isPromisesArray? [] : {}; + if(!i) { + defer.resolve(res); + return defer.promise(); } - 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(); + 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? [] : {}; } - else if(!rejectedPromises) { - (fulfilledPromises || (fulfilledPromises = isPromisesArray? [] : {}))[key] = vow.valueOf(promise); + + 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; - } + if(rejectedPromises) { + throw rejectedPromises; + } - return fulfilledPromises; - }); - }, + return fulfilledPromises; + }); + }, + + /** + * Returns a promise, that will be fulfilled if any of the items in `iterable` is fulfilled. + * If all of the `iterable` items get rejected, the promise will be 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; - /** - * Returns a promise, that will be fulfilled if any of the items in `iterable` is fulfilled. - * If all of the `iterable` items get rejected, the promise will be 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(); + } - 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); - 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, that will be fulfilled only when any of the items in `iterable` is fulfilled. + * If any of the `iterable` items gets rejected, the promise will be rejected. + * + * @param {Array} iterable + * @returns {vow:Promise} + */ + anyResolved : function(iterable) { + var defer = new Deferred(), + len = iterable.length; - return defer.promise(); - }, + if(!len) { + defer.reject(Error()); + return defer.promise(); + } - /** - * Returns a promise, that will be fulfilled only when any of the items in `iterable` is fulfilled. - * If any of the `iterable` items gets rejected, the promise will be rejected. - * - * @param {Array} iterable - * @returns {vow:Promise} - */ - anyResolved : function(iterable) { - var defer = new Deferred(), - len = iterable.length; + vow._forEach( + iterable, + defer.resolve, + defer.reject, + defer.notify, + defer); - if(!len) { - defer.reject(Error()); return defer.promise(); - } - - vow._forEach( - iterable, - defer.resolve, - defer.reject, - defer.notify, - defer); + }, + + /** + * Static equivalent to `promise.delay`. + * If `value` is not a promise, then `value` is treated as a 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 `value` is not a promise, then `value` is treated as a 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; - return defer.promise(); - }, + while(i < len) { + vow.when( + promises[keys? keys[i] : i], + wrapOnFulfilled(onFulfilled, i), + onRejected, + onProgress, + ctx); + ++i; + } + }, - /** - * Static equivalent to `promise.delay`. - * If `value` is not a promise, then `value` is treated as a fulfilled promise. - * - * @param {*} value - * @param {Number} delay - * @returns {vow:Promise} - */ - delay : function(value, delay) { - return vow.resolve(value).delay(delay); - }, + TimedOutError : defineCustomErrorType('TimedOut') + }; - /** - * Static equivalent to `promise.timeout`. - * If `value` is not a promise, then `value` is treated as a 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], - wrapOnFulfilled(onFulfilled, i), - onRejected, - onProgress, - ctx); - ++i; - } - }, - - TimedOutError : defineCustomErrorType('TimedOut') -}; - -var defineAsGlobal = true; -if(typeof module === 'object' && typeof module.exports === 'object') { - module.exports = vow; - defineAsGlobal = false; -} - -if(typeof modules === 'object' && isFunction(modules.define)) { - modules.define('vow', function(provide) { - provide(vow); - }); - defineAsGlobal = false; -} - -if(typeof define === 'function') { - define(function(require, exports, module) { + var defineAsGlobal = true; + if(typeof module === 'object' && typeof module.exports === 'object') { module.exports = vow; - }); - defineAsGlobal = false; -} + defineAsGlobal = false; + } + + if(typeof modules === 'object' && isFunction(modules.define)) { + 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); + defineAsGlobal && (global.vow = vow); -})(typeof window !== 'undefined'? window : global); + })(typeof window !== 'undefined'? window : global); diff --git a/package.json b/package.json index 56e2f5a69..2be804a9b 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "jshint": "^2.9.1", "jshint-groups": "^0.8.0", "mocha": "^3.3.0", - "vow": "^0.4.12" + "vow": "^0.4.17" }, "scripts": { "start": "magic server",