kitchensink
Version:
Dispatch's awesome components and style guide
1,332 lines (1,193 loc) • 39.8 kB
JavaScript
/**
* @module vow
* @author Filatov Dmitry <dfilatov@yandex-team.ru>
* @version 0.4.12
* @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) {
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);
};
}
var MutationObserver = global.MutationObserver || global.WebKitMutationObserver; // modern browsers
if(MutationObserver) {
var num = 1,
node = document.createTextNode('');
new MutationObserver(callFns).observe(node, { characterData : true });
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);
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;
},
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);
};
};
/**
* @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 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);
}
},
/**
* 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;
},
/**
* 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) {
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);
};
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);
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(new vow.TimedOutError('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(),
isRejected = this.isRejected();
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 if(isFulfilled) {
defer.resolve(arg);
}
else if(isRejected) {
defer.reject(arg);
}
else {
defer.notify(arg);
}
}
});
}
};
/** @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);
}
};
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 `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];
}
}
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();
}
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();
},
/**
* 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);
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, 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();
}
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;
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 `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;
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) {
module.exports = vow;
});
defineAsGlobal = false;
}
defineAsGlobal && (global.vow = vow);
})(typeof window !== 'undefined'? window : global);