/** * @module vow * @author Filatov Dmitry * @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);