js: Upgrade When.js from 2.0.0 to 2.4.0

This commit is contained in:
Stein Magnus Jodal 2013-09-17 00:39:13 +02:00
parent 94079be1c6
commit ba64c7f15a
2 changed files with 334 additions and 199 deletions

View File

@ -9,27 +9,30 @@
*
* @author Brian Cavalier
* @author John Hann
* @version 2.0.0
* @version 2.4.0
*/
(function(define) { 'use strict';
define(function () {
(function(define, global) { 'use strict';
define(function (require) {
// Public API
when.defer = defer; // Create a deferred
when.promise = promise; // Create a pending promise
when.resolve = resolve; // Create a resolved promise
when.reject = reject; // Create a rejected promise
when.defer = defer; // Create a {promise, resolver} pair
when.join = join; // Join 2 or more promises
when.all = all; // Resolve a list of promises
when.map = map; // Array.map() for promises
when.reduce = reduce; // Array.reduce() for promises
when.settle = settle; // Settle a list of promises
when.any = any; // One-winner race
when.some = some; // Multi-winner race
when.isPromise = isPromise; // Determine if a thing is a promise
when.isPromise = isPromiseLike; // DEPRECATED: use isPromiseLike
when.isPromiseLike = isPromiseLike; // Is something promise-like, aka thenable
/**
* Register an observer for a promise or immediate value.
@ -57,13 +60,35 @@ define(function () {
* a trusted when.js promise. Any other duck-typed promise is considered
* untrusted.
* @constructor
* @param {function} sendMessage function to deliver messages to the promise's handler
* @param {function?} inspect function that reports the promise's state
* @name Promise
*/
function Promise(then) {
this.then = then;
function Promise(sendMessage, inspect) {
this._message = sendMessage;
this.inspect = inspect;
}
Promise.prototype = {
/**
* Register handlers for this promise.
* @param [onFulfilled] {Function} fulfillment handler
* @param [onRejected] {Function} rejection handler
* @param [onProgress] {Function} progress handler
* @return {Promise} new Promise
*/
then: function(onFulfilled, onRejected, onProgress) {
/*jshint unused:false*/
var args, sendMessage;
args = arguments;
sendMessage = this._message;
return _promise(function(resolve, reject, notify) {
sendMessage('when', args, resolve, notify);
}, this._status && this._status.observed());
},
/**
* Register a rejection handler. Shortcut for .then(undefined, onRejected)
* @param {function?} onRejected
@ -84,9 +109,7 @@ define(function () {
* @returns {Promise}
*/
ensure: function(onFulfilledOrRejected) {
var self = this;
return this.then(injectHandler, injectHandler).yield(self);
return this.then(injectHandler, injectHandler)['yield'](this);
function injectHandler() {
return resolve(onFulfilledOrRejected());
@ -107,6 +130,16 @@ define(function () {
});
},
/**
* Runs a side effect when this promise fulfills, without changing the
* fulfillment value.
* @param {function} onFulfilledSideEffect
* @returns {Promise}
*/
tap: function(onFulfilledSideEffect) {
return this.then(onFulfilledSideEffect)['yield'](this);
},
/**
* Assumes that this promise will fulfill with an array, and arranges
* for the onFulfilled to be called with the array as its argument list
@ -162,13 +195,16 @@ define(function () {
}
/**
* Creates a new Deferred with fully isolated resolver and promise parts,
* either or both of which may be given out safely to consumers.
* Creates a {promise, resolver} pair, either or both of which
* may be given out safely to consumers.
* The resolver has resolve, reject, and progress. The promise
* only has then.
* has then plus extended promise API.
*
* @return {{
* promise: Promise,
* resolve: function:Promise,
* reject: function:Promise,
* notify: function:Promise
* resolver: {
* resolve: function:Promise,
* reject: function:Promise,
@ -216,12 +252,26 @@ define(function () {
/**
* Creates a new promise whose fate is determined by resolver.
* @private (for now)
* @param {function} resolver function(resolve, reject, notify)
* @returns {Promise} promise whose fate is determine by resolver
*/
function promise(resolver) {
var value, handlers = [];
return _promise(resolver, monitorApi.PromiseStatus && monitorApi.PromiseStatus());
}
/**
* Creates a new promise, linked to parent, whose fate is determined
* by resolver.
* @param {function} resolver function(resolve, reject, notify)
* @param {Promise?} status promise from which the new promise is begotten
* @returns {Promise} promise whose fate is determine by resolver
* @private
*/
function _promise(resolver, status) {
var self, value, consumers = [];
self = new Promise(_message, inspect);
self._status = status;
// Call the provider resolver to seal the promise's fate
try {
@ -231,29 +281,34 @@ define(function () {
}
// Return the promise
return new Promise(then);
return self;
/**
* Register handlers for this promise.
* @param [onFulfilled] {Function} fulfillment handler
* @param [onRejected] {Function} rejection handler
* @param [onProgress] {Function} progress handler
* @return {Promise} new Promise
* Private message delivery. Queues and delivers messages to
* the promise's ultimate fulfillment value or rejection reason.
* @private
* @param {String} type
* @param {Array} args
* @param {Function} resolve
* @param {Function} notify
*/
function then(onFulfilled, onRejected, onProgress) {
return promise(function(resolve, reject, notify) {
handlers
// Call handlers later, after resolution
? handlers.push(function(value) {
value.then(onFulfilled, onRejected, onProgress)
.then(resolve, reject, notify);
})
// Call handlers soon, but not in the current stack
: enqueue(function() {
value.then(onFulfilled, onRejected, onProgress)
.then(resolve, reject, notify);
});
});
function _message(type, args, resolve, notify) {
consumers ? consumers.push(deliver) : enqueue(function() { deliver(value); });
function deliver(p) {
p._message(type, args, resolve, notify);
}
}
/**
* Returns a snapshot of the promise's state at the instant inspect()
* is called. The returned object is not live and will not update as
* the promise's state changes.
* @returns {{ state:String, value?:*, reason?:* }} status snapshot
* of the promise.
*/
function inspect() {
return value ? value.inspect() : toPendingState();
}
/**
@ -262,14 +317,17 @@ define(function () {
* @param {*|Promise} val resolution value
*/
function promiseResolve(val) {
if(!handlers) {
if(!consumers) {
return;
}
value = coerce(val);
scheduleHandlers(handlers, value);
scheduleConsumers(consumers, value);
consumers = undef;
handlers = undef;
if(status) {
updateStatus(value, status);
}
}
/**
@ -285,27 +343,90 @@ define(function () {
* @param {*} update progress event payload to pass to all listeners
*/
function promiseNotify(update) {
if(handlers) {
scheduleHandlers(handlers, progressing(update));
if(consumers) {
scheduleConsumers(consumers, progressed(update));
}
}
}
/**
* Creates a fulfilled, local promise as a proxy for a value
* NOTE: must never be exposed
* @param {*} value fulfillment value
* @returns {Promise}
*/
function fulfilled(value) {
return near(
new NearFulfilledProxy(value),
function() { return toFulfilledState(value); }
);
}
/**
* Creates a rejected, local promise with the supplied reason
* NOTE: must never be exposed
* @param {*} reason rejection reason
* @returns {Promise}
*/
function rejected(reason) {
return near(
new NearRejectedProxy(reason),
function() { return toRejectedState(reason); }
);
}
/**
* Creates a near promise using the provided proxy
* NOTE: must never be exposed
* @param {object} proxy proxy for the promise's ultimate value or reason
* @param {function} inspect function that returns a snapshot of the
* returned near promise's state
* @returns {Promise}
*/
function near(proxy, inspect) {
return new Promise(function (type, args, resolve) {
try {
resolve(proxy[type].apply(proxy, args));
} catch(e) {
resolve(rejected(e));
}
}, inspect);
}
/**
* Create a progress promise with the supplied update.
* @private
* @param {*} update
* @return {Promise} progress promise
*/
function progressed(update) {
return new Promise(function (type, args, _, notify) {
var onProgress = args[2];
try {
notify(typeof onProgress === 'function' ? onProgress(update) : update);
} catch(e) {
notify(e);
}
});
}
/**
* Coerces x to a trusted Promise
*
* @private
* @param {*} x thing to coerce
* @returns {Promise} Guaranteed to return a trusted Promise. If x
* @returns {*} Guaranteed to return a trusted Promise. If x
* is trusted, returns x, otherwise, returns a new, trusted, already-resolved
* Promise whose resolution value is:
* * the resolution value of x if it's a foreign promise, or
* * x if it's a value
*/
function coerce(x) {
if(x instanceof Promise) {
if (x instanceof Promise) {
return x;
} else if (x !== Object(x)) {
}
if (!(x === Object(x) && 'then' in x)) {
return fulfilled(x);
}
@ -332,61 +453,34 @@ define(function () {
}
/**
* Create an already-fulfilled promise for the supplied value
* @private
* Proxy for a near, fulfilled value
* @param {*} value
* @return {Promise} fulfilled promise
* @constructor
*/
function fulfilled(value) {
var self = new Promise(function (onFulfilled) {
try {
return typeof onFulfilled == 'function'
? coerce(onFulfilled(value)) : self;
} catch (e) {
return rejected(e);
}
});
return self;
function NearFulfilledProxy(value) {
this.value = value;
}
NearFulfilledProxy.prototype.when = function(onResult) {
return typeof onResult === 'function' ? onResult(this.value) : this.value;
};
/**
* Create an already-rejected promise with the supplied rejection reason.
* @private
* Proxy for a near rejection
* @param {*} reason
* @return {Promise} rejected promise
* @constructor
*/
function rejected(reason) {
var self = new Promise(function (_, onRejected) {
try {
return typeof onRejected == 'function'
? coerce(onRejected(reason)) : self;
} catch (e) {
return rejected(e);
}
});
return self;
function NearRejectedProxy(reason) {
this.reason = reason;
}
/**
* Create a progress promise with the supplied update.
* @private
* @param {*} update
* @return {Promise} progress promise
*/
function progressing(update) {
var self = new Promise(function (_, __, onProgress) {
try {
return typeof onProgress == 'function'
? progressing(onProgress(update)) : self;
} catch (e) {
return progressing(e);
}
});
return self;
}
NearRejectedProxy.prototype.when = function(_, onError) {
if(typeof onError === 'function') {
return onError(this.reason);
} else {
throw this.reason;
}
};
/**
* Schedule a task that will process a list of handlers
@ -395,7 +489,7 @@ define(function () {
* @param {Array} handlers queue of handlers to execute
* @param {*} value passed as the only arg to each handler
*/
function scheduleHandlers(handlers, value) {
function scheduleConsumers(handlers, value) {
enqueue(function() {
var handler, i = 0;
while (handler = handlers[i++]) {
@ -404,14 +498,23 @@ define(function () {
});
}
function updateStatus(value, status) {
value.then(statusFulfilled, statusRejected);
function statusFulfilled() { status.fulfilled(); }
function statusRejected(r) { status.rejected(r); }
}
/**
* Determines if promiseOrValue is a promise or not
*
* @param {*} promiseOrValue anything
* @returns {boolean} true if promiseOrValue is a {@link Promise}
* Determines if x is promise-like, i.e. a thenable object
* NOTE: Will return true for *any thenable object*, and isn't truly
* safe, since it may attempt to access the `then` property of x (i.e.
* clever/malicious getters may do weird things)
* @param {*} x anything
* @returns {boolean} true if x is promise-like
*/
function isPromise(promiseOrValue) {
return promiseOrValue && typeof promiseOrValue.then === 'function';
function isPromiseLike(x) {
return x && typeof x.then === 'function';
}
/**
@ -423,17 +526,15 @@ define(function () {
* @param {Array} promisesOrValues array of anything, may contain a mix
* of promises and values
* @param howMany {number} number of promisesOrValues to resolve
* @param {function?} [onFulfilled] resolution handler
* @param {function?} [onRejected] rejection handler
* @param {function?} [onProgress] progress handler
* @param {function?} [onFulfilled] DEPRECATED, use returnedPromise.then()
* @param {function?} [onRejected] DEPRECATED, use returnedPromise.then()
* @param {function?} [onProgress] DEPRECATED, use returnedPromise.then()
* @returns {Promise} promise that will resolve to an array of howMany values that
* resolved first, or will reject with an array of
* (promisesOrValues.length - howMany) + 1 rejection reasons.
*/
function some(promisesOrValues, howMany, onFulfilled, onRejected, onProgress) {
checkCallbacks(2, arguments);
return when(promisesOrValues, function(promisesOrValues) {
return promise(resolveSome).then(onFulfilled, onRejected, onProgress);
@ -457,7 +558,7 @@ define(function () {
rejectOne = function(reason) {
reasons.push(reason);
if(!--toReject) {
fulfillOne = rejectOne = noop;
fulfillOne = rejectOne = identity;
reject(reasons);
}
};
@ -466,7 +567,7 @@ define(function () {
// This orders the values based on promise resolution order
values.push(val);
if (!--toResolve) {
fulfillOne = rejectOne = noop;
fulfillOne = rejectOne = identity;
resolve(values);
}
};
@ -496,9 +597,9 @@ define(function () {
*
* @param {Array|Promise} promisesOrValues array of anything, may contain a mix
* of {@link Promise}s and values
* @param {function?} [onFulfilled] resolution handler
* @param {function?} [onRejected] rejection handler
* @param {function?} [onProgress] progress handler
* @param {function?} [onFulfilled] DEPRECATED, use returnedPromise.then()
* @param {function?} [onRejected] DEPRECATED, use returnedPromise.then()
* @param {function?} [onProgress] DEPRECATED, use returnedPromise.then()
* @returns {Promise} promise that will resolve to the value that resolved first, or
* will reject with an array of all rejected inputs.
*/
@ -519,14 +620,13 @@ define(function () {
*
* @param {Array|Promise} promisesOrValues array of anything, may contain a mix
* of {@link Promise}s and values
* @param {function?} [onFulfilled] resolution handler
* @param {function?} [onRejected] rejection handler
* @param {function?} [onProgress] progress handler
* @param {function?} [onFulfilled] DEPRECATED, use returnedPromise.then()
* @param {function?} [onRejected] DEPRECATED, use returnedPromise.then()
* @param {function?} [onProgress] DEPRECATED, use returnedPromise.then()
* @returns {Promise}
*/
function all(promisesOrValues, onFulfilled, onRejected, onProgress) {
checkCallbacks(1, arguments);
return map(promisesOrValues, identity).then(onFulfilled, onRejected, onProgress);
return _map(promisesOrValues, identity).then(onFulfilled, onRejected, onProgress);
}
/**
@ -535,28 +635,49 @@ define(function () {
* have fulfilled, or will reject when *any one* of the input promises rejects.
*/
function join(/* ...promises */) {
return map(arguments, identity);
return _map(arguments, identity);
}
/**
* Traditional map function, similar to `Array.prototype.map()`, but allows
* input to contain {@link Promise}s and/or values, and mapFunc may return
* either a value or a {@link Promise}
*
* @param {Array|Promise} array array of anything, may contain a mix
* of {@link Promise}s and values
* @param {function} mapFunc mapping function mapFunc(value) which may return
* either a {@link Promise} or value
* @returns {Promise} a {@link Promise} that will resolve to an array containing
* the mapped output values.
* Settles all input promises such that they are guaranteed not to
* be pending once the returned promise fulfills. The returned promise
* will always fulfill, except in the case where `array` is a promise
* that rejects.
* @param {Array|Promise} array or promise for array of promises to settle
* @returns {Promise} promise that always fulfills with an array of
* outcome snapshots for each input promise.
*/
function settle(array) {
return _map(array, toFulfilledState, toRejectedState);
}
/**
* Promise-aware array map function, similar to `Array.prototype.map()`,
* but input array may contain promises or values.
* @param {Array|Promise} array array of anything, may contain promises and values
* @param {function} mapFunc map function which may return a promise or value
* @returns {Promise} promise that will fulfill with an array of mapped values
* or reject if any input promise rejects.
*/
function map(array, mapFunc) {
return _map(array, mapFunc);
}
/**
* Internal map that allows a fallback to handle rejections
* @param {Array|Promise} array array of anything, may contain promises and values
* @param {function} mapFunc map function which may return a promise or value
* @param {function?} fallback function to handle rejected promises
* @returns {Promise} promise that will fulfill with an array of mapped values
* or reject if any input promise rejects.
*/
function _map(array, mapFunc, fallback) {
return when(array, function(array) {
return promise(resolveMap);
return _promise(resolveMap);
function resolveMap(resolve, reject, notify) {
var results, len, toResolve, resolveOne, i;
var results, len, toResolve, i;
// Since we know the resulting length, we can preallocate the results
// array to avoid array expansions.
@ -565,27 +686,28 @@ define(function () {
if(!toResolve) {
resolve(results);
} else {
return;
}
resolveOne = function(item, i) {
when(item, mapFunc).then(function(mapped) {
results[i] = mapped;
if(!--toResolve) {
resolve(results);
}
}, reject, notify);
};
// Since mapFunc may be async, get all invocations of it into flight
for(i = 0; i < len; i++) {
if(i in array) {
resolveOne(array[i], i);
} else {
--toResolve;
}
// Since mapFunc may be async, get all invocations of it into flight
for(i = 0; i < len; i++) {
if(i in array) {
resolveOne(array[i], i);
} else {
--toResolve;
}
}
function resolveOne(item, i) {
when(item, mapFunc, fallback).then(function(mapped) {
results[i] = mapped;
notify(mapped);
if(!--toResolve) {
resolve(results);
}
}, reject);
}
}
});
}
@ -625,12 +747,46 @@ define(function () {
});
}
// Snapshot states
/**
* Creates a fulfilled state snapshot
* @private
* @param {*} x any value
* @returns {{state:'fulfilled',value:*}}
*/
function toFulfilledState(x) {
return { state: 'fulfilled', value: x };
}
/**
* Creates a rejected state snapshot
* @private
* @param {*} x any reason
* @returns {{state:'rejected',reason:*}}
*/
function toRejectedState(x) {
return { state: 'rejected', reason: x };
}
/**
* Creates a pending state snapshot
* @private
* @returns {{state:'pending'}}
*/
function toPendingState() {
return { state: 'pending' };
}
//
// Utilities, etc.
// Internals, utilities, etc.
//
var reduceArray, slice, fcall, nextTick, handlerQueue,
timeout, funcProto, call, arrayProto, undef;
setTimeout, funcProto, call, arrayProto, monitorApi,
cjsRequire, undef;
cjsRequire = require;
//
// Shared handler queue processing
@ -648,20 +804,13 @@ define(function () {
*/
function enqueue(task) {
if(handlerQueue.push(task) === 1) {
scheduleDrainQueue();
nextTick(drainQueue);
}
}
/**
* Schedule the queue to be drained in the next tick.
*/
function scheduleDrainQueue() {
nextTick(drainQueue);
}
/**
* Drain the handler queue entirely or partially, being careful to allow
* the queue to be extended while it is being processed, and to continue
* Drain the handler queue entirely, being careful to allow the
* queue to be extended while it is being processed, and to continue
* processing until it is truly empty.
*/
function drainQueue() {
@ -674,20 +823,36 @@ define(function () {
handlerQueue = [];
}
//
// Capture function and array utils
//
/*global setImmediate:true*/
// capture setTimeout to avoid being caught by fake timers
// used in time based tests
setTimeout = global.setTimeout;
// capture setTimeout to avoid being caught by fake timers used in time based tests
timeout = setTimeout;
nextTick = typeof setImmediate === 'function'
? typeof window === 'undefined'
? setImmediate
: setImmediate.bind(window)
: typeof process === 'object'
? process.nextTick
: function(task) { timeout(task, 0); };
// Allow attaching the monitor to when() if env has no console
monitorApi = typeof console != 'undefined' ? console : when;
// Prefer setImmediate or MessageChannel, cascade to node,
// vertx and finally setTimeout
/*global setImmediate,MessageChannel,process*/
if (typeof setImmediate === 'function') {
nextTick = setImmediate.bind(global);
} else if(typeof MessageChannel !== 'undefined') {
var channel = new MessageChannel();
channel.port1.onmessage = drainQueue;
nextTick = function() { channel.port2.postMessage(0); };
} else if (typeof process === 'object' && process.nextTick) {
nextTick = process.nextTick;
} else {
try {
// vert.x 1.x || 2.x
nextTick = cjsRequire('vertx').runOnLoop || cjsRequire('vertx').runOnContext;
} catch(ignore) {
nextTick = function(t) { setTimeout(t, 0); };
}
}
//
// Capture/polyfill function and array utils
//
// Safe function calls
funcProto = Function.prototype;
@ -748,40 +913,10 @@ define(function () {
return reduced;
};
//
// Utility functions
//
/**
* Helper that checks arrayOfCallbacks to ensure that each element is either
* a function, or null or undefined.
* @private
* @param {number} start index at which to start checking items in arrayOfCallbacks
* @param {Array} arrayOfCallbacks array to check
* @throws {Error} if any element of arrayOfCallbacks is something other than
* a functions, null, or undefined.
*/
function checkCallbacks(start, arrayOfCallbacks) {
// TODO: Promises/A+ update type checking and docs
var arg, i = arrayOfCallbacks.length;
while(i > start) {
arg = arrayOfCallbacks[--i];
if (arg != null && typeof arg != 'function') {
throw new Error('arg '+i+' must be a function');
}
}
}
function noop() {}
function identity(x) {
return x;
}
return when;
});
})(
typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(); }
);
})(typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); }, this);

View File

@ -16,7 +16,7 @@
"dependencies": {
"bane": "~1.0.0",
"faye-websocket": "~0.4.4",
"when": "~2.0.0"
"when": "~2.4.0"
},
"devDependencies": {
"buster": "~0.6.12",