1269 lines
37 KiB
JavaScript
1269 lines
37 KiB
JavaScript
/**
|
|
* @module vow
|
|
* @author Filatov Dmitry <dfilatov@yandex-team.ru>
|
|
* @version 0.4.4
|
|
* @license
|
|
* Dual licensed under the MIT and GPL licenses:
|
|
* * http://www.opensource.org/licenses/mit-license.php
|
|
* * http://www.gnu.org/licenses/gpl.html
|
|
*/
|
|
|
|
(function(global) {
|
|
|
|
/**
|
|
* @class Deferred
|
|
* @exports vow:Deferred
|
|
* @description
|
|
* The `Deferred` class is used to encapsulate newly-created promise object along with functions that resolve, reject or notify it.
|
|
*/
|
|
|
|
/**
|
|
* @constructor
|
|
* @description
|
|
* You can use `vow.defer()` instead of using this constructor.
|
|
*
|
|
* `new vow.Deferred()` gives the same result as `vow.defer()`.
|
|
*/
|
|
var Deferred = function() {
|
|
this._promise = new Promise();
|
|
};
|
|
|
|
Deferred.prototype = /** @lends Deferred.prototype */{
|
|
/**
|
|
* Returns corresponding promise.
|
|
*
|
|
* @returns {vow:Promise}
|
|
*/
|
|
promise : function() {
|
|
return this._promise;
|
|
},
|
|
|
|
/**
|
|
* Resolves corresponding promise with given `value`.
|
|
*
|
|
* @param {*} value
|
|
*
|
|
* @example
|
|
* ```js
|
|
* var defer = vow.defer(),
|
|
* promise = defer.promise();
|
|
*
|
|
* promise.then(function(value) {
|
|
* // value is "'success'" here
|
|
* });
|
|
*
|
|
* defer.resolve('success');
|
|
* ```
|
|
*/
|
|
resolve : function(value) {
|
|
this._promise.isResolved() || this._promise._resolve(value);
|
|
},
|
|
|
|
/**
|
|
* Rejects corresponding promise with given `reason`.
|
|
*
|
|
* @param {*} reason
|
|
*
|
|
* @example
|
|
* ```js
|
|
* var defer = vow.defer(),
|
|
* promise = defer.promise();
|
|
*
|
|
* promise.fail(function(reason) {
|
|
* // reason is "'something is wrong'" here
|
|
* });
|
|
*
|
|
* defer.reject('something is wrong');
|
|
* ```
|
|
*/
|
|
reject : function(reason) {
|
|
this._promise.isResolved() || this._promise._reject(reason);
|
|
},
|
|
|
|
/**
|
|
* Notifies corresponding promise with given `value`.
|
|
*
|
|
* @param {*} value
|
|
*
|
|
* @example
|
|
* ```js
|
|
* var defer = vow.defer(),
|
|
* promise = defer.promise();
|
|
*
|
|
* promise.progress(function(value) {
|
|
* // value is "'20%'", "'40%'" here
|
|
* });
|
|
*
|
|
* defer.notify('20%');
|
|
* defer.notify('40%');
|
|
* ```
|
|
*/
|
|
notify : function(value) {
|
|
this._promise.isResolved() || this._promise._notify(value);
|
|
}
|
|
};
|
|
|
|
var PROMISE_STATUS = {
|
|
PENDING : 0,
|
|
FULFILLED : 1,
|
|
REJECTED : -1
|
|
};
|
|
|
|
/**
|
|
* @class Promise
|
|
* @exports vow:Promise
|
|
* @description
|
|
* The `Promise` class is used when you want to give to the caller something to subscribe to,
|
|
* but not the ability to resolve or reject the deferred.
|
|
*/
|
|
|
|
/**
|
|
* @constructor
|
|
* @param {Function} resolver See https://github.com/domenic/promises-unwrapping/blob/master/README.md#the-promise-constructor for details.
|
|
* @description
|
|
* You should use this constructor directly only if you are going to use `vow` as DOM Promises implementation.
|
|
* In other case you should use `vow.defer()` and `defer.promise()` methods.
|
|
* @example
|
|
* ```js
|
|
* function fetchJSON(url) {
|
|
* return new vow.Promise(function(resolve, reject, notify) {
|
|
* var xhr = new XMLHttpRequest();
|
|
* xhr.open('GET', url);
|
|
* xhr.responseType = 'json';
|
|
* xhr.send();
|
|
* xhr.onload = function() {
|
|
* if(xhr.response) {
|
|
* resolve(xhr.response);
|
|
* }
|
|
* else {
|
|
* reject(new TypeError());
|
|
* }
|
|
* };
|
|
* });
|
|
* }
|
|
* ```
|
|
*/
|
|
var Promise = function(resolver) {
|
|
this._value = undef;
|
|
this._status = PROMISE_STATUS.PENDING;
|
|
|
|
this._fulfilledCallbacks = [];
|
|
this._rejectedCallbacks = [];
|
|
this._progressCallbacks = [];
|
|
|
|
if(resolver) { // NOTE: see https://github.com/domenic/promises-unwrapping/blob/master/README.md
|
|
var _this = this,
|
|
resolverFnLen = resolver.length;
|
|
|
|
resolver(
|
|
function(val) {
|
|
_this.isResolved() || _this._resolve(val);
|
|
},
|
|
resolverFnLen > 1?
|
|
function(reason) {
|
|
_this.isResolved() || _this._reject(reason);
|
|
} :
|
|
undef,
|
|
resolverFnLen > 2?
|
|
function(val) {
|
|
_this.isResolved() || _this._notify(val);
|
|
} :
|
|
undef);
|
|
}
|
|
};
|
|
|
|
Promise.prototype = /** @lends Promise.prototype */ {
|
|
/**
|
|
* Returns value of fulfilled promise or reason in case of rejection.
|
|
*
|
|
* @returns {*}
|
|
*/
|
|
valueOf : function() {
|
|
return this._value;
|
|
},
|
|
|
|
/**
|
|
* Returns `true` if promise is resolved.
|
|
*
|
|
* @returns {Boolean}
|
|
*/
|
|
isResolved : function() {
|
|
return this._status !== PROMISE_STATUS.PENDING;
|
|
},
|
|
|
|
/**
|
|
* Returns `true` if promise is fulfilled.
|
|
*
|
|
* @returns {Boolean}
|
|
*/
|
|
isFulfilled : function() {
|
|
return this._status === PROMISE_STATUS.FULFILLED;
|
|
},
|
|
|
|
/**
|
|
* Returns `true` if promise is rejected.
|
|
*
|
|
* @returns {Boolean}
|
|
*/
|
|
isRejected : function() {
|
|
return this._status === PROMISE_STATUS.REJECTED;
|
|
},
|
|
|
|
/**
|
|
* Adds reactions to promise.
|
|
*
|
|
* @param {Function} [onFulfilled] Callback that will to be invoked with the value after promise has been fulfilled
|
|
* @param {Function} [onRejected] Callback that will to be invoked with the reason after promise has been rejected
|
|
* @param {Function} [onProgress] Callback that will to be invoked with the value after promise has been notified
|
|
* @param {Object} [ctx] Context of callbacks execution
|
|
* @returns {vow:Promise} A new promise, see https://github.com/promises-aplus/promises-spec for details
|
|
*/
|
|
then : function(onFulfilled, onRejected, onProgress, ctx) {
|
|
var defer = new Deferred();
|
|
this._addCallbacks(defer, onFulfilled, onRejected, onProgress, ctx);
|
|
return defer.promise();
|
|
},
|
|
|
|
/**
|
|
* Adds rejection reaction only. It is shortcut for `promise.then(undefined, onRejected)`.
|
|
*
|
|
* @param {Function} onRejected Callback to be called with the value after promise has been rejected
|
|
* @param {Object} [ctx] Context of callback execution
|
|
* @returns {vow:Promise}
|
|
*/
|
|
'catch' : function(onRejected, ctx) {
|
|
return this.then(undef, onRejected, ctx);
|
|
},
|
|
|
|
/**
|
|
* Adds rejection reaction only. It is shortcut for `promise.then(null, onRejected)`. It's alias for `catch`.
|
|
*
|
|
* @param {Function} onRejected Callback to be called with the value after promise has been rejected
|
|
* @param {Object} [ctx] Context of callback execution
|
|
* @returns {vow:Promise}
|
|
*/
|
|
fail : function(onRejected, ctx) {
|
|
return this.then(undef, onRejected, ctx);
|
|
},
|
|
|
|
/**
|
|
* Adds resolving reaction (to fulfillment and rejection both).
|
|
*
|
|
* @param {Function} onResolved Callback that to be called with the value after promise has been rejected
|
|
* @param {Object} [ctx] Context of callback execution
|
|
* @returns {vow:Promise}
|
|
*/
|
|
always : function(onResolved, ctx) {
|
|
var _this = this,
|
|
cb = function() {
|
|
return onResolved.call(this, _this);
|
|
};
|
|
|
|
return this.then(cb, cb, ctx);
|
|
},
|
|
|
|
/**
|
|
* Adds progress reaction.
|
|
*
|
|
* @param {Function} onProgress Callback to be called with the value when promise has been notified
|
|
* @param {Object} [ctx] Context of callback execution
|
|
* @returns {vow:Promise}
|
|
*/
|
|
progress : function(onProgress, ctx) {
|
|
return this.then(undef, undef, onProgress, ctx);
|
|
},
|
|
|
|
/**
|
|
* Like `promise.then`, but "spreads" the array into a variadic value handler.
|
|
* It is useful with `vow.all` and `vow.allResolved` methods.
|
|
*
|
|
* @param {Function} [onFulfilled] Callback that will to be invoked with the value after promise has been fulfilled
|
|
* @param {Function} [onRejected] Callback that will to be invoked with the reason after promise has been rejected
|
|
* @param {Object} [ctx] Context of callbacks execution
|
|
* @returns {vow:Promise}
|
|
*
|
|
* @example
|
|
* ```js
|
|
* var defer1 = vow.defer(),
|
|
* defer2 = vow.defer();
|
|
*
|
|
* vow.all([defer1.promise(), defer2.promise()]).spread(function(arg1, arg2) {
|
|
* // arg1 is "1", arg2 is "'two'" here
|
|
* });
|
|
*
|
|
* defer1.resolve(1);
|
|
* defer2.resolve('two');
|
|
* ```
|
|
*/
|
|
spread : function(onFulfilled, onRejected, ctx) {
|
|
return this.then(
|
|
function(val) {
|
|
return onFulfilled.apply(this, val);
|
|
},
|
|
onRejected,
|
|
ctx);
|
|
},
|
|
|
|
/**
|
|
* Like `then`, but terminates a chain of promises.
|
|
* If the promise has been rejected, throws it as an exception in a future turn of the event loop.
|
|
*
|
|
* @param {Function} [onFulfilled] Callback that will to be invoked with the value after promise has been fulfilled
|
|
* @param {Function} [onRejected] Callback that will to be invoked with the reason after promise has been rejected
|
|
* @param {Function} [onProgress] Callback that will to be invoked with the value after promise has been notified
|
|
* @param {Object} [ctx] Context of callbacks execution
|
|
*
|
|
* @example
|
|
* ```js
|
|
* var defer = vow.defer();
|
|
* defer.reject(Error('Internal error'));
|
|
* defer.promise().done(); // exception to be thrown
|
|
* ```
|
|
*/
|
|
done : function(onFulfilled, onRejected, onProgress, ctx) {
|
|
this
|
|
.then(onFulfilled, onRejected, onProgress, ctx)
|
|
.fail(throwException);
|
|
},
|
|
|
|
/**
|
|
* Returns a new promise that will be fulfilled in `delay` milliseconds if the promise is fulfilled,
|
|
* or immediately rejected if promise is rejected.
|
|
*
|
|
* @param {Number} delay
|
|
* @returns {vow:Promise}
|
|
*/
|
|
delay : function(delay) {
|
|
var timer,
|
|
promise = this.then(function(val) {
|
|
var defer = new Deferred();
|
|
timer = setTimeout(
|
|
function() {
|
|
defer.resolve(val);
|
|
},
|
|
delay);
|
|
|
|
return defer.promise();
|
|
});
|
|
|
|
promise.always(function() {
|
|
clearTimeout(timer);
|
|
});
|
|
|
|
return promise;
|
|
},
|
|
|
|
/**
|
|
* Returns a new promise that will be rejected in `timeout` milliseconds
|
|
* if the promise is not resolved beforehand.
|
|
*
|
|
* @param {Number} timeout
|
|
* @returns {vow:Promise}
|
|
*
|
|
* @example
|
|
* ```js
|
|
* var defer = vow.defer(),
|
|
* promiseWithTimeout1 = defer.promise().timeout(50),
|
|
* promiseWithTimeout2 = defer.promise().timeout(200);
|
|
*
|
|
* setTimeout(
|
|
* function() {
|
|
* defer.resolve('ok');
|
|
* },
|
|
* 100);
|
|
*
|
|
* promiseWithTimeout1.fail(function(reason) {
|
|
* // promiseWithTimeout to be rejected in 50ms
|
|
* });
|
|
*
|
|
* promiseWithTimeout2.then(function(value) {
|
|
* // promiseWithTimeout to be fulfilled with "'ok'" value
|
|
* });
|
|
* ```
|
|
*/
|
|
timeout : function(timeout) {
|
|
var defer = new Deferred(),
|
|
timer = setTimeout(
|
|
function() {
|
|
defer.reject(Error('timed out'));
|
|
},
|
|
timeout);
|
|
|
|
this.then(
|
|
function(val) {
|
|
defer.resolve(val);
|
|
},
|
|
function(reason) {
|
|
defer.reject(reason);
|
|
});
|
|
|
|
defer.promise().always(function() {
|
|
clearTimeout(timer);
|
|
});
|
|
|
|
return defer.promise();
|
|
},
|
|
|
|
_vow : true,
|
|
|
|
_resolve : function(val) {
|
|
if(this._status !== PROMISE_STATUS.PENDING) {
|
|
return;
|
|
}
|
|
|
|
if(val === this) {
|
|
this._reject(TypeError('Can\'t resolve promise with itself'));
|
|
return;
|
|
}
|
|
|
|
if(val && !!val._vow) { // shortpath for vow.Promise
|
|
val.then(
|
|
this._resolve,
|
|
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.PENDING) {
|
|
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.PENDING) {
|
|
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.PENDING) {
|
|
this._progressCallbacks.push({ defer : defer, fn : onProgress, ctx : ctx });
|
|
}
|
|
},
|
|
|
|
_callCallbacks : function(callbacks, arg) {
|
|
var len = callbacks.length;
|
|
if(!len) {
|
|
return;
|
|
}
|
|
|
|
var isResolved = this.isResolved(),
|
|
isFulfilled = this.isFulfilled();
|
|
|
|
nextTick(function() {
|
|
var i = 0, cb, defer, fn;
|
|
while(i < len) {
|
|
cb = callbacks[i++];
|
|
defer = cb.defer;
|
|
fn = cb.fn;
|
|
|
|
if(fn) {
|
|
var ctx = cb.ctx,
|
|
res;
|
|
try {
|
|
res = ctx? fn.call(ctx, arg) : fn(arg);
|
|
}
|
|
catch(e) {
|
|
defer.reject(e);
|
|
continue;
|
|
}
|
|
|
|
isResolved?
|
|
defer.resolve(res) :
|
|
defer.notify(res);
|
|
}
|
|
else {
|
|
isResolved?
|
|
isFulfilled?
|
|
defer.resolve(arg) :
|
|
defer.reject(arg) :
|
|
defer.notify(arg);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
/** @lends Promise */
|
|
var staticMethods = {
|
|
/**
|
|
* Coerces given `value` to a promise, or returns the `value` if it's already a promise.
|
|
*
|
|
* @param {*} value
|
|
* @returns {vow:Promise}
|
|
*/
|
|
cast : function(value) {
|
|
return vow.cast(value);
|
|
},
|
|
|
|
/**
|
|
* Returns a promise to be fulfilled only after all the items in `iterable` are fulfilled,
|
|
* or to be rejected when any of the `iterable` is rejected.
|
|
*
|
|
* @param {Array|Object} iterable
|
|
* @returns {vow:Promise}
|
|
*/
|
|
all : function(iterable) {
|
|
return vow.all(iterable);
|
|
},
|
|
|
|
/**
|
|
* Returns a promise to be fulfilled only when any of the items in `iterable` are fulfilled,
|
|
* or to be rejected when the first item is rejected.
|
|
*
|
|
* @param {Array} iterable
|
|
* @returns {vow:Promise}
|
|
*/
|
|
race : function(iterable) {
|
|
return vow.anyResolved(iterable);
|
|
},
|
|
|
|
/**
|
|
* Returns a promise that has already been resolved with the given `value`.
|
|
* If `value` is a promise, returned promise will be adopted with the state of given promise.
|
|
*
|
|
* @param {*} value
|
|
* @returns {vow:Promise}
|
|
*/
|
|
resolve : function(value) {
|
|
return vow.resolve(value);
|
|
},
|
|
|
|
/**
|
|
* Returns a promise that has already been rejected with the given `reason`.
|
|
*
|
|
* @param {*} reason
|
|
* @returns {vow:Promise}
|
|
*/
|
|
reject : function(reason) {
|
|
return vow.reject(reason);
|
|
}
|
|
};
|
|
|
|
for(var prop in staticMethods) {
|
|
staticMethods.hasOwnProperty(prop) &&
|
|
(Promise[prop] = staticMethods[prop]);
|
|
}
|
|
|
|
var vow = /** @exports vow */ {
|
|
Deferred : Deferred,
|
|
|
|
Promise : Promise,
|
|
|
|
/**
|
|
* Creates a new deferred. This method is a factory method for `vow:Deferred` class.
|
|
* It's equivalent to `new vow.Deferred()`.
|
|
*
|
|
* @returns {vow:Deferred}
|
|
*/
|
|
defer : function() {
|
|
return new Deferred();
|
|
},
|
|
|
|
/**
|
|
* Static equivalent to `promise.then`.
|
|
* If given `value` is not a promise, then `value` is equivalent to fulfilled promise.
|
|
*
|
|
* @param {*} value
|
|
* @param {Function} [onFulfilled] Callback that will to be invoked with the value after promise has been fulfilled
|
|
* @param {Function} [onRejected] Callback that will to be invoked with the reason after promise has been rejected
|
|
* @param {Function} [onProgress] Callback that will to be invoked with the value after promise has been notified
|
|
* @param {Object} [ctx] Context of callbacks execution
|
|
* @returns {vow:Promise}
|
|
*/
|
|
when : function(value, onFulfilled, onRejected, onProgress, ctx) {
|
|
return vow.cast(value).then(onFulfilled, onRejected, onProgress, ctx);
|
|
},
|
|
|
|
/**
|
|
* Static equivalent to `promise.fail`.
|
|
* If given `value` is not a promise, then `value` is equivalent to fulfilled promise.
|
|
*
|
|
* @param {*} value
|
|
* @param {Function} onRejected Callback that will to be invoked with the reason after promise has been rejected
|
|
* @param {Object} [ctx] Context of callback execution
|
|
* @returns {vow:Promise}
|
|
*/
|
|
fail : function(value, onRejected, ctx) {
|
|
return vow.when(value, undef, onRejected, ctx);
|
|
},
|
|
|
|
/**
|
|
* Static equivalent to `promise.always`.
|
|
* If given `value` is not a promise, then `value` is equivalent to fulfilled promise.
|
|
*
|
|
* @param {*} value
|
|
* @param {Function} onResolved Callback that will to be invoked with the reason after promise has been resolved
|
|
* @param {Object} [ctx] Context of callback execution
|
|
* @returns {vow:Promise}
|
|
*/
|
|
always : function(value, onResolved, ctx) {
|
|
return vow.when(value).always(onResolved, ctx);
|
|
},
|
|
|
|
/**
|
|
* Static equivalent to `promise.progress`.
|
|
* If given `value` is not a promise, then `value` is equivalent to fulfilled promise.
|
|
*
|
|
* @param {*} value
|
|
* @param {Function} onProgress Callback that will to be invoked with the reason after promise has been notified
|
|
* @param {Object} [ctx] Context of callback execution
|
|
* @returns {vow:Promise}
|
|
*/
|
|
progress : function(value, onProgress, ctx) {
|
|
return vow.when(value).progress(onProgress, ctx);
|
|
},
|
|
|
|
/**
|
|
* Static equivalent to `promise.spread`.
|
|
* If given `value` is not a promise, then `value` is equivalent to fulfilled promise.
|
|
*
|
|
* @param {*} value
|
|
* @param {Function} [onFulfilled] Callback that will to be invoked with the value after promise has been fulfilled
|
|
* @param {Function} [onRejected] Callback that will to be invoked with the reason after promise has been rejected
|
|
* @param {Object} [ctx] Context of callbacks execution
|
|
* @returns {vow:Promise}
|
|
*/
|
|
spread : function(value, onFulfilled, onRejected, ctx) {
|
|
return vow.when(value).spread(onFulfilled, onRejected, ctx);
|
|
},
|
|
|
|
/**
|
|
* Static equivalent to `promise.done`.
|
|
* If given `value` is not a promise, then `value` is equivalent to fulfilled promise.
|
|
*
|
|
* @param {*} value
|
|
* @param {Function} [onFulfilled] Callback that will to be invoked with the value after promise has been fulfilled
|
|
* @param {Function} [onRejected] Callback that will to be invoked with the reason after promise has been rejected
|
|
* @param {Function} [onProgress] Callback that will to be invoked with the value after promise has been notified
|
|
* @param {Object} [ctx] Context of callbacks execution
|
|
*/
|
|
done : function(value, onFulfilled, onRejected, onProgress, ctx) {
|
|
vow.when(value).done(onFulfilled, onRejected, onProgress, ctx);
|
|
},
|
|
|
|
/**
|
|
* Checks whether the given `value` is a promise-like object
|
|
*
|
|
* @param {*} value
|
|
* @returns {Boolean}
|
|
*
|
|
* @example
|
|
* ```js
|
|
* vow.isPromise('something'); // returns false
|
|
* vow.isPromise(vow.defer().promise()); // returns true
|
|
* vow.isPromise({ then : function() { }); // returns true
|
|
* ```
|
|
*/
|
|
isPromise : function(value) {
|
|
return isObject(value) && isFunction(value.then);
|
|
},
|
|
|
|
/**
|
|
* Coerces given `value` to a promise, or returns the `value` if it's already a promise.
|
|
*
|
|
* @param {*} value
|
|
* @returns {vow:Promise}
|
|
*/
|
|
cast : function(value) {
|
|
return vow.isPromise(value)?
|
|
value :
|
|
vow.resolve(value);
|
|
},
|
|
|
|
/**
|
|
* Static equivalent to `promise.valueOf`.
|
|
* If given `value` is not an instance of `vow.Promise`, then `value` is equivalent to fulfilled promise.
|
|
*
|
|
* @param {*} value
|
|
* @returns {*}
|
|
*/
|
|
valueOf : function(value) {
|
|
return value && isFunction(value.valueOf)? value.valueOf() : value;
|
|
},
|
|
|
|
/**
|
|
* Static equivalent to `promise.isFulfilled`.
|
|
* If given `value` is not an instance of `vow.Promise`, then `value` is equivalent to fulfilled promise.
|
|
*
|
|
* @param {*} value
|
|
* @returns {Boolean}
|
|
*/
|
|
isFulfilled : function(value) {
|
|
return value && isFunction(value.isFulfilled)? value.isFulfilled() : true;
|
|
},
|
|
|
|
/**
|
|
* Static equivalent to `promise.isRejected`.
|
|
* If given `value` is not an instance of `vow.Promise`, then `value` is equivalent to fulfilled promise.
|
|
*
|
|
* @param {*} value
|
|
* @returns {Boolean}
|
|
*/
|
|
isRejected : function(value) {
|
|
return value && isFunction(value.isRejected)? value.isRejected() : false;
|
|
},
|
|
|
|
/**
|
|
* Static equivalent to `promise.isResolved`.
|
|
* If given `value` is not a promise, then `value` is equivalent to fulfilled promise.
|
|
*
|
|
* @param {*} value
|
|
* @returns {Boolean}
|
|
*/
|
|
isResolved : function(value) {
|
|
return value && isFunction(value.isResolved)? value.isResolved() : true;
|
|
},
|
|
|
|
/**
|
|
* Returns a promise that has already been resolved with the given `value`.
|
|
* If `value` is a promise, returned promise will be adopted with the state of given promise.
|
|
*
|
|
* @param {*} value
|
|
* @returns {vow:Promise}
|
|
*/
|
|
resolve : function(value) {
|
|
var res = vow.defer();
|
|
res.resolve(value);
|
|
return res.promise();
|
|
},
|
|
|
|
/**
|
|
* Returns a promise that has already been fulfilled with the given `value`.
|
|
* If `value` is a promise, returned promise will be fulfilled with fulfill/rejection value of given promise.
|
|
*
|
|
* @param {*} value
|
|
* @returns {vow:Promise}
|
|
*/
|
|
fulfill : function(value) {
|
|
return vow.when(value, null, function(reason) {
|
|
return reason;
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Returns a promise that has already been rejected with the given `reason`.
|
|
* If `reason` is a promise, returned promise will be rejected with fulfill/rejection value of given promise.
|
|
*
|
|
* @param {*} reason
|
|
* @returns {vow:Promise}
|
|
*/
|
|
reject : function(reason) {
|
|
return vow.when(reason, function(val) {
|
|
throw val;
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Invokes a given function `fn` with arguments `args`
|
|
*
|
|
* @param {Function} fn
|
|
* @param {...*} [args]
|
|
* @returns {vow:Promise}
|
|
*
|
|
* @example
|
|
* ```js
|
|
* var promise1 = vow.invoke(function(value) {
|
|
* return value;
|
|
* }, 'ok'),
|
|
* promise2 = vow.invoke(function() {
|
|
* throw Error();
|
|
* });
|
|
*
|
|
* promise1.isFulfilled(); // true
|
|
* promise1.valueOf(); // 'ok'
|
|
* promise2.isRejected(); // true
|
|
* promise2.valueOf(); // instance of Error
|
|
* ```
|
|
*/
|
|
invoke : function(fn, args) {
|
|
var len = Math.max(arguments.length - 1, 0),
|
|
callArgs;
|
|
if(len) { // optimization for V8
|
|
callArgs = Array(len);
|
|
var i = 0;
|
|
while(i < len) {
|
|
callArgs[i++] = arguments[i];
|
|
}
|
|
}
|
|
|
|
try {
|
|
return vow.resolve(callArgs?
|
|
fn.apply(global, callArgs) :
|
|
fn.call(global));
|
|
}
|
|
catch(e) {
|
|
return vow.reject(e);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Returns a promise to be fulfilled only after all the items in `iterable` are fulfilled,
|
|
* or to be rejected when any of the `iterable` is rejected.
|
|
*
|
|
* @param {Array|Object} iterable
|
|
* @returns {vow:Promise}
|
|
*
|
|
* @example
|
|
* with array:
|
|
* ```js
|
|
* var defer1 = vow.defer(),
|
|
* defer2 = vow.defer();
|
|
*
|
|
* vow.all([defer1.promise(), defer2.promise(), 3])
|
|
* .then(function(value) {
|
|
* // value is "[1, 2, 3]" here
|
|
* });
|
|
*
|
|
* defer1.resolve(1);
|
|
* defer2.resolve(2);
|
|
* ```
|
|
*
|
|
* @example
|
|
* with object:
|
|
* ```js
|
|
* var defer1 = vow.defer(),
|
|
* defer2 = vow.defer();
|
|
*
|
|
* vow.all({ p1 : defer1.promise(), p2 : defer2.promise(), p3 : 3 })
|
|
* .then(function(value) {
|
|
* // value is "{ p1 : 1, p2 : 2, p3 : 3 }" here
|
|
* });
|
|
*
|
|
* defer1.resolve(1);
|
|
* defer2.resolve(2);
|
|
* ```
|
|
*/
|
|
all : function(iterable) {
|
|
var defer = new Deferred(),
|
|
isPromisesArray = isArray(iterable),
|
|
keys = isPromisesArray?
|
|
getArrayKeys(iterable) :
|
|
getObjectKeys(iterable),
|
|
len = keys.length,
|
|
res = isPromisesArray? [] : {};
|
|
|
|
if(!len) {
|
|
defer.resolve(res);
|
|
return defer.promise();
|
|
}
|
|
|
|
var i = len;
|
|
vow._forEach(
|
|
iterable,
|
|
function() {
|
|
if(!--i) {
|
|
var j = 0;
|
|
while(j < len) {
|
|
res[keys[j]] = vow.valueOf(iterable[keys[j++]]);
|
|
}
|
|
defer.resolve(res);
|
|
}
|
|
},
|
|
defer.reject,
|
|
defer.notify,
|
|
defer,
|
|
keys);
|
|
|
|
return defer.promise();
|
|
},
|
|
|
|
/**
|
|
* Returns a promise to be fulfilled only after all the items in `iterable` are resolved.
|
|
*
|
|
* @param {Array|Object} iterable
|
|
* @returns {vow:Promise}
|
|
*
|
|
* @example
|
|
* ```js
|
|
* var defer1 = vow.defer(),
|
|
* defer2 = vow.defer();
|
|
*
|
|
* vow.allResolved([defer1.promise(), defer2.promise()]).spread(function(promise1, promise2) {
|
|
* promise1.isRejected(); // returns true
|
|
* promise1.valueOf(); // returns "'error'"
|
|
* promise2.isFulfilled(); // returns true
|
|
* promise2.valueOf(); // returns "'ok'"
|
|
* });
|
|
*
|
|
* defer1.reject('error');
|
|
* defer2.resolve('ok');
|
|
* ```
|
|
*/
|
|
allResolved : function(iterable) {
|
|
var defer = new Deferred(),
|
|
isPromisesArray = isArray(iterable),
|
|
keys = isPromisesArray?
|
|
getArrayKeys(iterable) :
|
|
getObjectKeys(iterable),
|
|
i = keys.length,
|
|
res = isPromisesArray? [] : {};
|
|
|
|
if(!i) {
|
|
defer.resolve(res);
|
|
return defer.promise();
|
|
}
|
|
|
|
var onResolved = function() {
|
|
--i || defer.resolve(iterable);
|
|
};
|
|
|
|
vow._forEach(
|
|
iterable,
|
|
onResolved,
|
|
onResolved,
|
|
defer.notify,
|
|
defer,
|
|
keys);
|
|
|
|
return defer.promise();
|
|
},
|
|
|
|
allPatiently : function(iterable) {
|
|
return vow.allResolved(iterable).then(function() {
|
|
var isPromisesArray = isArray(iterable),
|
|
keys = isPromisesArray?
|
|
getArrayKeys(iterable) :
|
|
getObjectKeys(iterable),
|
|
rejectedPromises, fulfilledPromises,
|
|
len = keys.length, i = 0, key, promise;
|
|
|
|
if(!len) {
|
|
return isPromisesArray? [] : {};
|
|
}
|
|
|
|
while(i < len) {
|
|
key = keys[i++];
|
|
promise = iterable[key];
|
|
if(vow.isRejected(promise)) {
|
|
rejectedPromises || (rejectedPromises = isPromisesArray? [] : {});
|
|
isPromisesArray?
|
|
rejectedPromises.push(promise.valueOf()) :
|
|
rejectedPromises[key] = promise.valueOf();
|
|
}
|
|
else if(!rejectedPromises) {
|
|
(fulfilledPromises || (fulfilledPromises = isPromisesArray? [] : {}))[key] = vow.valueOf(promise);
|
|
}
|
|
}
|
|
|
|
if(rejectedPromises) {
|
|
throw rejectedPromises;
|
|
}
|
|
|
|
return fulfilledPromises;
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Returns a promise to be fulfilled only when any of the items in `iterable` are fulfilled,
|
|
* or to be rejected when all the items are rejected (with the reason of the first rejected item).
|
|
*
|
|
* @param {Array} iterable
|
|
* @returns {vow:Promise}
|
|
*/
|
|
any : function(iterable) {
|
|
var defer = new Deferred(),
|
|
len = iterable.length;
|
|
|
|
if(!len) {
|
|
defer.reject(Error());
|
|
return defer.promise();
|
|
}
|
|
|
|
var i = 0, reason;
|
|
vow._forEach(
|
|
iterable,
|
|
defer.resolve,
|
|
function(e) {
|
|
i || (reason = e);
|
|
++i === len && defer.reject(reason);
|
|
},
|
|
defer.notify,
|
|
defer);
|
|
|
|
return defer.promise();
|
|
},
|
|
|
|
/**
|
|
* Returns a promise to be fulfilled only when any of the items in `iterable` are fulfilled,
|
|
* or to be rejected when the first item is rejected.
|
|
*
|
|
* @param {Array} iterable
|
|
* @returns {vow:Promise}
|
|
*/
|
|
anyResolved : function(iterable) {
|
|
var defer = new Deferred(),
|
|
len = iterable.length;
|
|
|
|
if(!len) {
|
|
defer.reject(Error());
|
|
return defer.promise();
|
|
}
|
|
|
|
vow._forEach(
|
|
iterable,
|
|
defer.resolve,
|
|
defer.reject,
|
|
defer.notify,
|
|
defer);
|
|
|
|
return defer.promise();
|
|
},
|
|
|
|
/**
|
|
* Static equivalent to `promise.delay`.
|
|
* If given `value` is not a promise, then `value` is equivalent to fulfilled promise.
|
|
*
|
|
* @param {*} value
|
|
* @param {Number} delay
|
|
* @returns {vow:Promise}
|
|
*/
|
|
delay : function(value, delay) {
|
|
return vow.resolve(value).delay(delay);
|
|
},
|
|
|
|
/**
|
|
* Static equivalent to `promise.timeout`.
|
|
* If given `value` is not a promise, then `value` is equivalent to fulfilled promise.
|
|
*
|
|
* @param {*} value
|
|
* @param {Number} timeout
|
|
* @returns {vow:Promise}
|
|
*/
|
|
timeout : function(value, timeout) {
|
|
return vow.resolve(value).timeout(timeout);
|
|
},
|
|
|
|
_forEach : function(promises, onFulfilled, onRejected, onProgress, ctx, keys) {
|
|
var len = keys? keys.length : promises.length,
|
|
i = 0;
|
|
while(i < len) {
|
|
vow.when(promises[keys? keys[i] : i], onFulfilled, onRejected, onProgress, ctx);
|
|
++i;
|
|
}
|
|
}
|
|
};
|
|
|
|
var undef,
|
|
nextTick = (function() {
|
|
var fns = [],
|
|
enqueueFn = function(fn) {
|
|
return fns.push(fn) === 1;
|
|
},
|
|
callFns = function() {
|
|
var fnsToCall = fns, i = 0, len = fns.length;
|
|
fns = [];
|
|
while(i < len) {
|
|
fnsToCall[i++]();
|
|
}
|
|
};
|
|
|
|
if(typeof setImmediate === 'function') { // ie10, nodejs >= 0.10
|
|
return function(fn) {
|
|
enqueueFn(fn) && setImmediate(callFns);
|
|
};
|
|
}
|
|
|
|
if(typeof process === 'object' && process.nextTick) { // nodejs < 0.10
|
|
return function(fn) {
|
|
enqueueFn(fn) && process.nextTick(callFns);
|
|
};
|
|
}
|
|
|
|
if(global.postMessage) { // modern browsers
|
|
var isPostMessageAsync = true;
|
|
if(global.attachEvent) {
|
|
var checkAsync = function() {
|
|
isPostMessageAsync = false;
|
|
};
|
|
global.attachEvent('onmessage', checkAsync);
|
|
global.postMessage('__checkAsync', '*');
|
|
global.detachEvent('onmessage', checkAsync);
|
|
}
|
|
|
|
if(isPostMessageAsync) {
|
|
var msg = '__promise' + +new Date,
|
|
onMessage = function(e) {
|
|
if(e.data === msg) {
|
|
e.stopPropagation && e.stopPropagation();
|
|
callFns();
|
|
}
|
|
};
|
|
|
|
global.addEventListener?
|
|
global.addEventListener('message', onMessage, true) :
|
|
global.attachEvent('onmessage', onMessage);
|
|
|
|
return function(fn) {
|
|
enqueueFn(fn) && global.postMessage(msg, '*');
|
|
};
|
|
}
|
|
}
|
|
|
|
var doc = global.document;
|
|
if('onreadystatechange' in doc.createElement('script')) { // ie6-ie8
|
|
var createScript = function() {
|
|
var script = doc.createElement('script');
|
|
script.onreadystatechange = function() {
|
|
script.parentNode.removeChild(script);
|
|
script = script.onreadystatechange = null;
|
|
callFns();
|
|
};
|
|
(doc.documentElement || doc.body).appendChild(script);
|
|
};
|
|
|
|
return function(fn) {
|
|
enqueueFn(fn) && createScript();
|
|
};
|
|
}
|
|
|
|
return function(fn) { // old browsers
|
|
enqueueFn(fn) && setTimeout(callFns, 0);
|
|
};
|
|
})(),
|
|
throwException = function(e) {
|
|
nextTick(function() {
|
|
throw e;
|
|
});
|
|
},
|
|
isFunction = function(obj) {
|
|
return typeof obj === 'function';
|
|
},
|
|
isObject = function(obj) {
|
|
return obj !== null && typeof obj === 'object';
|
|
},
|
|
toStr = Object.prototype.toString,
|
|
isArray = Array.isArray || function(obj) {
|
|
return toStr.call(obj) === '[object Array]';
|
|
},
|
|
getArrayKeys = function(arr) {
|
|
var res = [],
|
|
i = 0, len = arr.length;
|
|
while(i < len) {
|
|
res.push(i++);
|
|
}
|
|
return res;
|
|
},
|
|
getObjectKeys = Object.keys || function(obj) {
|
|
var res = [];
|
|
for(var i in obj) {
|
|
obj.hasOwnProperty(i) && res.push(i);
|
|
}
|
|
return res;
|
|
};
|
|
|
|
var defineAsGlobal = true;
|
|
if(typeof exports === 'object') {
|
|
module.exports = vow;
|
|
defineAsGlobal = false;
|
|
}
|
|
|
|
if(typeof modules === 'object') {
|
|
modules.define('vow', function(provide) {
|
|
provide(vow);
|
|
});
|
|
defineAsGlobal = false;
|
|
}
|
|
|
|
if(typeof define === 'function') {
|
|
define(function(require, exports, module) {
|
|
module.exports = vow;
|
|
});
|
|
defineAsGlobal = false;
|
|
}
|
|
|
|
defineAsGlobal && (global.vow = vow);
|
|
|
|
})(this);
|