js: Upgrade when.js from 1.6.1 to 1.8.1

This commit is contained in:
Stein Magnus Jodal 2013-03-12 23:39:14 +01:00
parent 51b782e926
commit cf2d22c281
3 changed files with 412 additions and 356 deletions

View File

@ -1,4 +1,4 @@
/** @license MIT License (c) copyright B Cavalier & J Hann */ /** @license MIT License (c) copyright 2011-2013 original author or authors */
/** /**
* A lightweight CommonJS Promises/A and when() implementation * A lightweight CommonJS Promises/A and when() implementation
@ -7,11 +7,14 @@
* Licensed under the MIT License at: * Licensed under the MIT License at:
* http://www.opensource.org/licenses/mit-license.php * http://www.opensource.org/licenses/mit-license.php
* *
* @version 1.6.1 * @author Brian Cavalier
* @author John Hann
*
* @version 1.8.1
*/ */
(function(define) { 'use strict'; (function(define) { 'use strict';
define(['module'], function () { define(function () {
var reduceArray, slice, undef; var reduceArray, slice, undef;
// //
@ -25,110 +28,106 @@ define(['module'], function () {
when.join = join; // Join 2 or more promises when.join = join; // Join 2 or more promises
when.all = all; // Resolve a list of promises when.all = all; // Resolve a list of promises
when.some = some; // Resolve a sub-set of promises
when.any = any; // Resolve one promise in a list
when.map = map; // Array.map() for promises when.map = map; // Array.map() for promises
when.reduce = reduce; // Array.reduce() for promises when.reduce = reduce; // Array.reduce() for promises
when.any = any; // One-winner race
when.some = some; // Multi-winner race
when.chain = chain; // Make a promise trigger another resolver when.chain = chain; // Make a promise trigger another resolver
when.isPromise = isPromise; // Determine if a thing is a promise when.isPromise = isPromise; // Determine if a thing is a promise
/** /**
* Register an observer for a promise or immediate value. * Register an observer for a promise or immediate value.
* @function
* @name when
* @namespace
* *
* @param promiseOrValue {*} * @param {*} promiseOrValue
* @param {Function} [callback] callback to be called when promiseOrValue is * @param {function?} [onFulfilled] callback to be called when promiseOrValue is
* successfully fulfilled. If promiseOrValue is an immediate value, callback * successfully fulfilled. If promiseOrValue is an immediate value, callback
* will be invoked immediately. * will be invoked immediately.
* @param {Function} [errback] callback to be called when promiseOrValue is * @param {function?} [onRejected] callback to be called when promiseOrValue is
* rejected. * rejected.
* @param {Function} [progressHandler] callback to be called when progress updates * @param {function?} [onProgress] callback to be called when progress updates
* are issued for promiseOrValue. * are issued for promiseOrValue.
* @returns {Promise} a new {@link Promise} that will complete with the return * @returns {Promise} a new {@link Promise} that will complete with the return
* value of callback or errback or the completion value of promiseOrValue if * value of callback or errback or the completion value of promiseOrValue if
* callback and/or errback is not supplied. * callback and/or errback is not supplied.
*/ */
function when(promiseOrValue, callback, errback, progressHandler) { function when(promiseOrValue, onFulfilled, onRejected, onProgress) {
// Get a trusted promise for the input promiseOrValue, and then // Get a trusted promise for the input promiseOrValue, and then
// register promise handlers // register promise handlers
return resolve(promiseOrValue).then(callback, errback, progressHandler); return resolve(promiseOrValue).then(onFulfilled, onRejected, onProgress);
} }
/** /**
* Returns promiseOrValue if promiseOrValue is a {@link Promise}, a new Promise if * Returns promiseOrValue if promiseOrValue is a {@link Promise}, a new Promise if
* promiseOrValue is a foreign promise, or a new, already-fulfilled {@link Promise} * promiseOrValue is a foreign promise, or a new, already-fulfilled {@link Promise}
* whose value is promiseOrValue if promiseOrValue is an immediate value. * whose value is promiseOrValue if promiseOrValue is an immediate value.
* @memberOf when
* *
* @param promiseOrValue {*} * @param {*} promiseOrValue
* @returns Guaranteed to return a trusted Promise. If promiseOrValue is a when.js {@link Promise} * @returns {Promise} Guaranteed to return a trusted Promise. If promiseOrValue
* returns promiseOrValue, otherwise, returns a new, already-resolved, when.js {@link Promise} * is trusted, returns promiseOrValue, otherwise, returns a new, already-resolved
* whose resolution value is: * when.js promise whose resolution value is:
* * the resolution value of promiseOrValue if it's a foreign promise, or * * the resolution value of promiseOrValue if it's a foreign promise, or
* * promiseOrValue if it's a value * * promiseOrValue if it's a value
*/ */
function resolve(promiseOrValue) { function resolve(promiseOrValue) {
var promise, deferred; var promise;
if(promiseOrValue instanceof Promise) { if(promiseOrValue instanceof Promise) {
// It's a when.js promise, so we trust it // It's a when.js promise, so we trust it
promise = promiseOrValue; promise = promiseOrValue;
} else if(isPromise(promiseOrValue)) {
// Assimilate foreign promises
promise = assimilate(promiseOrValue);
} else { } else {
// It's not a when.js promise. See if it's a foreign promise or a value. // It's a value, create a fulfilled promise for it.
promise = fulfilled(promiseOrValue);
// Some promises, particularly Q promises, provide a valueOf method that
// attempts to synchronously return the fulfilled value of the promise, or
// returns the unresolved promise itself. Attempting to break a fulfillment
// value out of a promise appears to be necessary to break cycles between
// Q and When attempting to coerce each-other's promises in an infinite loop.
// For promises that do not implement "valueOf", the Object#valueOf is harmless.
// See: https://github.com/kriskowal/q/issues/106
// IMPORTANT: Must check for a promise here, since valueOf breaks other things
// like Date.
if (isPromise(promiseOrValue) && typeof promiseOrValue.valueOf === 'function') {
promiseOrValue = promiseOrValue.valueOf();
}
if(isPromise(promiseOrValue)) {
// It looks like a thenable, but we don't know where it came from,
// so we don't trust its implementation entirely. Introduce a trusted
// middleman when.js promise
deferred = defer();
// IMPORTANT: This is the only place when.js should ever call .then() on
// an untrusted promise.
promiseOrValue.then(deferred.resolve, deferred.reject, deferred.progress);
promise = deferred.promise;
} else {
// It's a value, not a promise. Create a resolved promise for it.
promise = fulfilled(promiseOrValue);
}
} }
return promise; return promise;
} }
/** /**
* Returns a rejected promise for the supplied promiseOrValue. If * Assimilate an untrusted thenable by introducing a trusted middle man.
* promiseOrValue is a value, it will be the rejection value of the * Not a perfect strategy, but possibly the best we can do.
* returned promise. If promiseOrValue is a promise, its * IMPORTANT: This is the only place when.js should ever call an untrusted
* completion value will be the rejected value of the returned promise * thenable's then() on an. Don't expose the return value to the untrusted thenable
* @memberOf when
* *
* @param promiseOrValue {*} the rejected value of the returned {@link Promise} * @param {*} thenable
* @param {function} thenable.then
* @returns {Promise}
*/
function assimilate(thenable) {
var d = defer();
// TODO: Enqueue this for future execution in 2.0
try {
thenable.then(
function(value) { d.resolve(value); },
function(reason) { d.reject(reason); },
function(update) { d.progress(update); }
);
} catch(e) {
d.reject(e);
}
return d.promise;
}
/**
* Returns a rejected promise for the supplied promiseOrValue. The returned
* promise will be rejected with:
* - promiseOrValue, if it is a value, or
* - if promiseOrValue is a promise
* - promiseOrValue's value after it is fulfilled
* - promiseOrValue's reason after it is rejected
* @param {*} promiseOrValue the rejected value of the returned {@link Promise}
* @return {Promise} rejected {@link Promise} * @return {Promise} rejected {@link Promise}
*/ */
function reject(promiseOrValue) { function reject(promiseOrValue) {
return when(promiseOrValue, function(value) { return when(promiseOrValue, rejected);
return rejected(value);
});
} }
/** /**
@ -145,25 +144,53 @@ define(['module'], function () {
Promise.prototype = { Promise.prototype = {
/** /**
* Register a callback that will be called when a promise is * Register a callback that will be called when a promise is
* resolved or rejected. Optionally also register a progress handler. * fulfilled or rejected. Optionally also register a progress handler.
* Shortcut for .then(alwaysback, alwaysback, progback) * Shortcut for .then(onFulfilledOrRejected, onFulfilledOrRejected, onProgress)
* @memberOf Promise * @param {function?} [onFulfilledOrRejected]
* @param alwaysback {Function} * @param {function?} [onProgress]
* @param progback {Function}
* @return {Promise} * @return {Promise}
*/ */
always: function(alwaysback, progback) { always: function(onFulfilledOrRejected, onProgress) {
return this.then(alwaysback, alwaysback, progback); return this.then(onFulfilledOrRejected, onFulfilledOrRejected, onProgress);
}, },
/** /**
* Register a rejection handler. Shortcut for .then(null, errback) * Register a rejection handler. Shortcut for .then(undefined, onRejected)
* @memberOf Promise * @param {function?} onRejected
* @param errback {Function}
* @return {Promise} * @return {Promise}
*/ */
otherwise: function(errback) { otherwise: function(onRejected) {
return this.then(undef, errback); return this.then(undef, onRejected);
},
/**
* Shortcut for .then(function() { return value; })
* @param {*} value
* @return {Promise} a promise that:
* - is fulfilled if value is not a promise, or
* - if value is a promise, will fulfill with its value, or reject
* with its reason.
*/
'yield': function(value) {
return this.then(function() {
return value;
});
},
/**
* Assumes that this promise will fulfill with an array, and arranges
* for the onFulfilled to be called with the array as its argument list
* i.e. onFulfilled.apply(undefined, array).
* @param {function} onFulfilled function to receive spread arguments
* @return {Promise}
*/
spread: function(onFulfilled) {
return this.then(function(array) {
// array may contain promises, so resolve its contents.
return all(array, function(array) {
return onFulfilled.apply(undef, array);
});
});
} }
}; };
@ -171,13 +198,13 @@ define(['module'], function () {
* Create an already-resolved promise for the supplied value * Create an already-resolved promise for the supplied value
* @private * @private
* *
* @param value anything * @param {*} value
* @return {Promise} * @return {Promise} fulfilled promise
*/ */
function fulfilled(value) { function fulfilled(value) {
var p = new Promise(function(callback) { var p = new Promise(function(onFulfilled) {
try { try {
return resolve(callback ? callback(value) : value); return resolve(typeof onFulfilled == 'function' ? onFulfilled(value) : value);
} catch(e) { } catch(e) {
return rejected(e); return rejected(e);
} }
@ -191,13 +218,13 @@ define(['module'], function () {
* rejection reason. * rejection reason.
* @private * @private
* *
* @param reason rejection reason * @param {*} reason
* @return {Promise} * @return {Promise} rejected promise
*/ */
function rejected(reason) { function rejected(reason) {
var p = new Promise(function(callback, errback) { var p = new Promise(function(_, onRejected) {
try { try {
return errback ? resolve(errback(reason)) : rejected(reason); return resolve(typeof onRejected == 'function' ? onRejected(reason) : rejected(reason));
} catch(e) { } catch(e) {
return rejected(e); return rejected(e);
} }
@ -212,14 +239,12 @@ define(['module'], function () {
* The Deferred itself has the full API: resolve, reject, progress, and * The Deferred itself has the full API: resolve, reject, progress, and
* then. The resolver has resolve, reject, and progress. The promise * then. The resolver has resolve, reject, and progress. The promise
* only has then. * only has then.
* @memberOf when
* @function
* *
* @return {Deferred} * @return {Deferred}
*/ */
function defer() { function defer() {
var deferred, promise, handlers, progressHandlers, var deferred, promise, handlers, progressHandlers,
_then, _progress, _resolve; _then, _notify, _resolve;
/** /**
* The promise for the new deferred * The promise for the new deferred
@ -233,18 +258,19 @@ define(['module'], function () {
* @name Deferred * @name Deferred
*/ */
deferred = { deferred = {
then: then, then: then, // DEPRECATED: use deferred.promise.then
resolve: promiseResolve, resolve: promiseResolve,
reject: promiseReject, reject: promiseReject,
// TODO: Consider renaming progress() to notify() progress: promiseNotify, // DEPRECATED: use deferred.notify
progress: promiseProgress, notify: promiseNotify,
promise: promise, promise: promise,
resolver: { resolver: {
resolve: promiseResolve, resolve: promiseResolve,
reject: promiseReject, reject: promiseReject,
progress: promiseProgress progress: promiseNotify, // DEPRECATED: use deferred.notify
notify: promiseNotify
} }
}; };
@ -256,29 +282,29 @@ define(['module'], function () {
* functions to the registered listeners * functions to the registered listeners
* @private * @private
* *
* @param [callback] {Function} resolution handler * @param {function?} [onFulfilled] resolution handler
* @param [errback] {Function} rejection handler * @param {function?} [onRejected] rejection handler
* @param [progback] {Function} progress handler * @param {function?} [onProgress] progress handler
* @throws {Error} if any argument is not null, undefined, or a Function
*/ */
_then = function(callback, errback, progback) { _then = function(onFulfilled, onRejected, onProgress) {
var deferred, progressHandler; var deferred, progressHandler;
deferred = defer(); deferred = defer();
progressHandler = progback
progressHandler = typeof onProgress === 'function'
? function(update) { ? function(update) {
try { try {
// Allow progress handler to transform progress event // Allow progress handler to transform progress event
deferred.progress(progback(update)); deferred.notify(onProgress(update));
} catch(e) { } catch(e) {
// Use caught value as progress // Use caught value as progress
deferred.progress(e); deferred.notify(e);
} }
} }
: deferred.progress; : function(update) { deferred.notify(update); };
handlers.push(function(promise) { handlers.push(function(promise) {
promise.then(callback, errback) promise.then(onFulfilled, onRejected)
.then(deferred.resolve, deferred.reject, progressHandler); .then(deferred.resolve, deferred.reject, progressHandler);
}); });
@ -290,9 +316,9 @@ define(['module'], function () {
/** /**
* Issue a progress event, notifying all progress listeners * Issue a progress event, notifying all progress listeners
* @private * @private
* @param update {*} progress event payload to pass to all listeners * @param {*} update progress event payload to pass to all listeners
*/ */
_progress = function(update) { _notify = function(update) {
processQueue(progressHandlers, update); processQueue(progressHandlers, update);
return update; return update;
}; };
@ -301,61 +327,58 @@ define(['module'], function () {
* Transition from pre-resolution state to post-resolution state, notifying * Transition from pre-resolution state to post-resolution state, notifying
* all listeners of the resolution or rejection * all listeners of the resolution or rejection
* @private * @private
* @param completed {Promise} the completed value of this deferred * @param {*} value the value of this deferred
*/ */
_resolve = function(completed) { _resolve = function(value) {
completed = resolve(completed);
// Replace _then with one that directly notifies with the result. // Replace _then with one that directly notifies with the result.
_then = completed.then; _then = value.then;
// Replace _resolve so that this Deferred can only be completed once // Replace _resolve so that this Deferred can only be resolved once
_resolve = resolve; _resolve = resolve;
// Make _progress a noop, to disallow progress for the resolved promise. // Make _progress a noop, to disallow progress for the resolved promise.
_progress = noop; _notify = identity;
// Notify handlers // Notify handlers
processQueue(handlers, completed); processQueue(handlers, value);
// Free progressHandlers array since we'll never issue progress events // Free progressHandlers array since we'll never issue progress events
progressHandlers = handlers = undef; progressHandlers = handlers = undef;
return completed; return value;
}; };
return deferred; return deferred;
/** /**
* Wrapper to allow _then to be replaced safely * Wrapper to allow _then to be replaced safely
* @param [callback] {Function} resolution handler * @param {function?} [onFulfilled] resolution handler
* @param [errback] {Function} rejection handler * @param {function?} [onRejected] rejection handler
* @param [progback] {Function} progress handler * @param {function?} [onProgress] progress handler
* @return {Promise} new Promise * @return {Promise} new promise
* @throws {Error} if any argument is not null, undefined, or a Function
*/ */
function then(callback, errback, progback) { function then(onFulfilled, onRejected, onProgress) {
return _then(callback, errback, progback); // TODO: Promises/A+ check typeof onFulfilled, onRejected, onProgress
return _then(onFulfilled, onRejected, onProgress);
} }
/** /**
* Wrapper to allow _resolve to be replaced * Wrapper to allow _resolve to be replaced
*/ */
function promiseResolve(val) { function promiseResolve(val) {
return _resolve(val); return _resolve(resolve(val));
} }
/** /**
* Wrapper to allow _resolve to be replaced * Wrapper to allow _reject to be replaced
*/ */
function promiseReject(err) { function promiseReject(err) {
return _resolve(rejected(err)); return _resolve(rejected(err));
} }
/** /**
* Wrapper to allow _progress to be replaced * Wrapper to allow _notify to be replaced
* @param {*} update progress update
*/ */
function promiseProgress(update) { function promiseNotify(update) {
return _progress(update); return _notify(update);
} }
} }
@ -365,7 +388,7 @@ define(['module'], function () {
* promiseOrValue is a promise. * promiseOrValue is a promise.
* *
* @param {*} promiseOrValue anything * @param {*} promiseOrValue anything
* @returns {Boolean} true if promiseOrValue is a {@link Promise} * @returns {boolean} true if promiseOrValue is a {@link Promise}
*/ */
function isPromise(promiseOrValue) { function isPromise(promiseOrValue) {
return promiseOrValue && typeof promiseOrValue.then === 'function'; return promiseOrValue && typeof promiseOrValue.then === 'function';
@ -376,25 +399,24 @@ define(['module'], function () {
* howMany of the supplied promisesOrValues have resolved, or will reject when * howMany of the supplied promisesOrValues have resolved, or will reject when
* it becomes impossible for howMany to resolve, for example, when * it becomes impossible for howMany to resolve, for example, when
* (promisesOrValues.length - howMany) + 1 input promises reject. * (promisesOrValues.length - howMany) + 1 input promises reject.
* @memberOf when
* *
* @param promisesOrValues {Array} array of anything, may contain a mix * @param {Array} promisesOrValues array of anything, may contain a mix
* of {@link Promise}s and values * of promises and values
* @param howMany {Number} number of promisesOrValues to resolve * @param howMany {number} number of promisesOrValues to resolve
* @param [callback] {Function} resolution handler * @param {function?} [onFulfilled] resolution handler
* @param [errback] {Function} rejection handler * @param {function?} [onRejected] rejection handler
* @param [progback] {Function} progress handler * @param {function?} [onProgress] progress handler
* @returns {Promise} promise that will resolve to an array of howMany values that * @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 * resolved first, or will reject with an array of (promisesOrValues.length - howMany) + 1
* rejection reasons. * rejection reasons.
*/ */
function some(promisesOrValues, howMany, callback, errback, progback) { function some(promisesOrValues, howMany, onFulfilled, onRejected, onProgress) {
checkCallbacks(2, arguments); checkCallbacks(2, arguments);
return when(promisesOrValues, function(promisesOrValues) { return when(promisesOrValues, function(promisesOrValues) {
var toResolve, toReject, values, reasons, deferred, fulfillOne, rejectOne, progress, len, i; var toResolve, toReject, values, reasons, deferred, fulfillOne, rejectOne, notify, len, i;
len = promisesOrValues.length >>> 0; len = promisesOrValues.length >>> 0;
@ -411,7 +433,7 @@ define(['module'], function () {
deferred.resolve(values); deferred.resolve(values);
} else { } else {
progress = deferred.progress; notify = deferred.notify;
rejectOne = function(reason) { rejectOne = function(reason) {
reasons.push(reason); reasons.push(reason);
@ -435,12 +457,12 @@ define(['module'], function () {
for(i = 0; i < len; ++i) { for(i = 0; i < len; ++i) {
if(i in promisesOrValues) { if(i in promisesOrValues) {
when(promisesOrValues[i], fulfiller, rejecter, progress); when(promisesOrValues[i], fulfiller, rejecter, notify);
} }
} }
} }
return deferred.then(callback, errback, progback); return deferred.promise.then(onFulfilled, onRejected, onProgress);
function rejecter(reason) { function rejecter(reason) {
rejectOne(reason); rejectOne(reason);
@ -457,23 +479,22 @@ define(['module'], function () {
* Initiates a competitive race, returning a promise that will resolve when * Initiates a competitive race, returning a promise that will resolve when
* any one of the supplied promisesOrValues has resolved or will reject when * any one of the supplied promisesOrValues has resolved or will reject when
* *all* promisesOrValues have rejected. * *all* promisesOrValues have rejected.
* @memberOf when
* *
* @param promisesOrValues {Array|Promise} array of anything, may contain a mix * @param {Array|Promise} promisesOrValues array of anything, may contain a mix
* of {@link Promise}s and values * of {@link Promise}s and values
* @param [callback] {Function} resolution handler * @param {function?} [onFulfilled] resolution handler
* @param [errback] {Function} rejection handler * @param {function?} [onRejected] rejection handler
* @param [progback] {Function} progress handler * @param {function?} [onProgress] progress handler
* @returns {Promise} promise that will resolve to the value that resolved first, or * @returns {Promise} promise that will resolve to the value that resolved first, or
* will reject with an array of all rejected inputs. * will reject with an array of all rejected inputs.
*/ */
function any(promisesOrValues, callback, errback, progback) { function any(promisesOrValues, onFulfilled, onRejected, onProgress) {
function unwrapSingleResult(val) { function unwrapSingleResult(val) {
return callback ? callback(val[0]) : val[0]; return onFulfilled ? onFulfilled(val[0]) : val[0];
} }
return some(promisesOrValues, 1, unwrapSingleResult, errback, progback); return some(promisesOrValues, 1, unwrapSingleResult, onRejected, onProgress);
} }
/** /**
@ -482,22 +503,20 @@ define(['module'], function () {
* containing the resolution values of each of the promisesOrValues. * containing the resolution values of each of the promisesOrValues.
* @memberOf when * @memberOf when
* *
* @param promisesOrValues {Array|Promise} array of anything, may contain a mix * @param {Array|Promise} promisesOrValues array of anything, may contain a mix
* of {@link Promise}s and values * of {@link Promise}s and values
* @param [callback] {Function} * @param {function?} [onFulfilled] resolution handler
* @param [errback] {Function} * @param {function?} [onRejected] rejection handler
* @param [progressHandler] {Function} * @param {function?} [onProgress] progress handler
* @returns {Promise} * @returns {Promise}
*/ */
function all(promisesOrValues, callback, errback, progressHandler) { function all(promisesOrValues, onFulfilled, onRejected, onProgress) {
checkCallbacks(1, arguments); checkCallbacks(1, arguments);
return map(promisesOrValues, identity).then(callback, errback, progressHandler); return map(promisesOrValues, identity).then(onFulfilled, onRejected, onProgress);
} }
/** /**
* Joins multiple promises into a single returned promise. * Joins multiple promises into a single returned promise.
* @memberOf when
* @param {Promise|*} [...promises] two or more promises to join
* @return {Promise} a promise that will fulfill when *all* the input promises * @return {Promise} a promise that will fulfill when *all* the input promises
* have fulfilled, or will reject when *any one* of the input promises rejects. * have fulfilled, or will reject when *any one* of the input promises rejects.
*/ */
@ -510,18 +529,16 @@ define(['module'], function () {
* input to contain {@link Promise}s and/or values, and mapFunc may return * input to contain {@link Promise}s and/or values, and mapFunc may return
* either a value or a {@link Promise} * either a value or a {@link Promise}
* *
* @memberOf when * @param {Array|Promise} promise array of anything, may contain a mix
*
* @param promise {Array|Promise} array of anything, may contain a mix
* of {@link Promise}s and values * of {@link Promise}s and values
* @param mapFunc {Function} mapping function mapFunc(value) which may return * @param {function} mapFunc mapping function mapFunc(value) which may return
* either a {@link Promise} or value * either a {@link Promise} or value
* @returns {Promise} a {@link Promise} that will resolve to an array containing * @returns {Promise} a {@link Promise} that will resolve to an array containing
* the mapped output values. * the mapped output values.
*/ */
function map(promise, mapFunc) { function map(promise, mapFunc) {
return when(promise, function(array) { return when(promise, function(array) {
var results, len, toResolve, resolve, reject, i, d; var results, len, toResolve, resolve, i, d;
// Since we know the resulting length, we can preallocate the results // Since we know the resulting length, we can preallocate the results
// array to avoid array expansions. // array to avoid array expansions.
@ -533,7 +550,6 @@ define(['module'], function () {
d.resolve(results); d.resolve(results);
} else { } else {
reject = d.reject;
resolve = function resolveOne(item, i) { resolve = function resolveOne(item, i) {
when(item, mapFunc).then(function(mapped) { when(item, mapFunc).then(function(mapped) {
results[i] = mapped; results[i] = mapped;
@ -541,7 +557,7 @@ define(['module'], function () {
if(!--toResolve) { if(!--toResolve) {
d.resolve(results); d.resolve(results);
} }
}, reject); }, d.reject);
}; };
// Since mapFunc may be async, get all invocations of it into flight // Since mapFunc may be async, get all invocations of it into flight
@ -562,18 +578,15 @@ define(['module'], function () {
/** /**
* Traditional reduce function, similar to `Array.prototype.reduce()`, but * Traditional reduce function, similar to `Array.prototype.reduce()`, but
* input may contain {@link Promise}s and/or values, and reduceFunc * input may contain promises and/or values, and reduceFunc
* may return either a value or a {@link Promise}, *and* initialValue may * may return either a value or a promise, *and* initialValue may
* be a {@link Promise} for the starting value. * be a promise for the starting value.
* @memberOf when
* *
* @param promise {Array|Promise} array of anything, may contain a mix * @param {Array|Promise} promise array or promise for an array of anything,
* of {@link Promise}s and values. May also be a {@link Promise} for * may contain a mix of promises and values.
* an array. * @param {function} reduceFunc reduce function reduce(currentValue, nextValue, index, total),
* @param reduceFunc {Function} reduce function reduce(currentValue, nextValue, index, total),
* where total is the total number of items being reduced, and will be the same * where total is the total number of items being reduced, and will be the same
* in each call to reduceFunc. * in each call to reduceFunc.
* @param [initialValue] {*} starting value, or a {@link Promise} for the starting value
* @returns {Promise} that will resolve to the final reduced value * @returns {Promise} that will resolve to the final reduced value
*/ */
function reduce(promise, reduceFunc /*, initialValue */) { function reduce(promise, reduceFunc /*, initialValue */) {
@ -599,13 +612,14 @@ define(['module'], function () {
} }
/** /**
* Ensure that resolution of promiseOrValue will complete resolver with the completion * Ensure that resolution of promiseOrValue will trigger resolver with the
* value of promiseOrValue, or instead with resolveValue if it is provided. * value or reason of promiseOrValue, or instead with resolveValue if it is provided.
* @memberOf when
* *
* @param promiseOrValue * @param promiseOrValue
* @param resolver {Resolver} * @param {Object} resolver
* @param [resolveValue] anything * @param {function} resolver.resolve
* @param {function} resolver.reject
* @param {*} [resolveValue]
* @returns {Promise} * @returns {Promise}
*/ */
function chain(promiseOrValue, resolver, resolveValue) { function chain(promiseOrValue, resolver, resolveValue) {
@ -613,10 +627,18 @@ define(['module'], function () {
return when(promiseOrValue, return when(promiseOrValue,
function(val) { function(val) {
return resolver.resolve(useResolveValue ? resolveValue : val); val = useResolveValue ? resolveValue : val;
resolver.resolve(val);
return val;
}, },
resolver.reject, function(reason) {
resolver.progress resolver.reject(reason);
return rejected(reason);
},
function(update) {
typeof resolver.notify === 'function' && resolver.notify(update);
return update;
}
); );
} }
@ -624,6 +646,11 @@ define(['module'], function () {
// Utility functions // Utility functions
// //
/**
* Apply all functions in queue to value
* @param {Array} queue array of functions to execute
* @param {*} value argument passed to each function
*/
function processQueue(queue, value) { function processQueue(queue, value) {
var handler, i = 0; var handler, i = 0;
@ -636,12 +663,13 @@ define(['module'], function () {
* Helper that checks arrayOfCallbacks to ensure that each element is either * Helper that checks arrayOfCallbacks to ensure that each element is either
* a function, or null or undefined. * a function, or null or undefined.
* @private * @private
* * @param {number} start index at which to start checking items in arrayOfCallbacks
* @param arrayOfCallbacks {Array} array to check * @param {Array} arrayOfCallbacks array to check
* @throws {Error} if any element of arrayOfCallbacks is something other than * @throws {Error} if any element of arrayOfCallbacks is something other than
* a Functions, null, or undefined. * a functions, null, or undefined.
*/ */
function checkCallbacks(start, arrayOfCallbacks) { function checkCallbacks(start, arrayOfCallbacks) {
// TODO: Promises/A+ update type checking and docs
var arg, i = arrayOfCallbacks.length; var arg, i = arrayOfCallbacks.length;
while(i > start) { while(i > start) {
@ -723,7 +751,7 @@ define(['module'], function () {
}); });
})(typeof define == 'function' && define.amd })(typeof define == 'function' && define.amd
? define ? define
: function (deps, factory) { typeof exports === 'object' : function (factory) { typeof exports === 'object'
? (module.exports = factory()) ? (module.exports = factory())
: (this.when = factory()); : (this.when = factory());
} }

View File

@ -173,11 +173,14 @@
* Licensed under the MIT License at: * Licensed under the MIT License at:
* http://www.opensource.org/licenses/mit-license.php * http://www.opensource.org/licenses/mit-license.php
* *
* @version 1.6.1 * @author Brian Cavalier
* @author John Hann
*
* @version 1.8.1
*/ */
(function(define) { 'use strict'; (function(define) { 'use strict';
define(['module'], function () { define(function () {
var reduceArray, slice, undef; var reduceArray, slice, undef;
// //
@ -191,110 +194,106 @@ define(['module'], function () {
when.join = join; // Join 2 or more promises when.join = join; // Join 2 or more promises
when.all = all; // Resolve a list of promises when.all = all; // Resolve a list of promises
when.some = some; // Resolve a sub-set of promises
when.any = any; // Resolve one promise in a list
when.map = map; // Array.map() for promises when.map = map; // Array.map() for promises
when.reduce = reduce; // Array.reduce() for promises when.reduce = reduce; // Array.reduce() for promises
when.any = any; // One-winner race
when.some = some; // Multi-winner race
when.chain = chain; // Make a promise trigger another resolver when.chain = chain; // Make a promise trigger another resolver
when.isPromise = isPromise; // Determine if a thing is a promise when.isPromise = isPromise; // Determine if a thing is a promise
/** /**
* Register an observer for a promise or immediate value. * Register an observer for a promise or immediate value.
* @function
* @name when
* @namespace
* *
* @param promiseOrValue {*} * @param {*} promiseOrValue
* @param {Function} [callback] callback to be called when promiseOrValue is * @param {function?} [onFulfilled] callback to be called when promiseOrValue is
* successfully fulfilled. If promiseOrValue is an immediate value, callback * successfully fulfilled. If promiseOrValue is an immediate value, callback
* will be invoked immediately. * will be invoked immediately.
* @param {Function} [errback] callback to be called when promiseOrValue is * @param {function?} [onRejected] callback to be called when promiseOrValue is
* rejected. * rejected.
* @param {Function} [progressHandler] callback to be called when progress updates * @param {function?} [onProgress] callback to be called when progress updates
* are issued for promiseOrValue. * are issued for promiseOrValue.
* @returns {Promise} a new {@link Promise} that will complete with the return * @returns {Promise} a new {@link Promise} that will complete with the return
* value of callback or errback or the completion value of promiseOrValue if * value of callback or errback or the completion value of promiseOrValue if
* callback and/or errback is not supplied. * callback and/or errback is not supplied.
*/ */
function when(promiseOrValue, callback, errback, progressHandler) { function when(promiseOrValue, onFulfilled, onRejected, onProgress) {
// Get a trusted promise for the input promiseOrValue, and then // Get a trusted promise for the input promiseOrValue, and then
// register promise handlers // register promise handlers
return resolve(promiseOrValue).then(callback, errback, progressHandler); return resolve(promiseOrValue).then(onFulfilled, onRejected, onProgress);
} }
/** /**
* Returns promiseOrValue if promiseOrValue is a {@link Promise}, a new Promise if * Returns promiseOrValue if promiseOrValue is a {@link Promise}, a new Promise if
* promiseOrValue is a foreign promise, or a new, already-fulfilled {@link Promise} * promiseOrValue is a foreign promise, or a new, already-fulfilled {@link Promise}
* whose value is promiseOrValue if promiseOrValue is an immediate value. * whose value is promiseOrValue if promiseOrValue is an immediate value.
* @memberOf when
* *
* @param promiseOrValue {*} * @param {*} promiseOrValue
* @returns Guaranteed to return a trusted Promise. If promiseOrValue is a when.js {@link Promise} * @returns {Promise} Guaranteed to return a trusted Promise. If promiseOrValue
* returns promiseOrValue, otherwise, returns a new, already-resolved, when.js {@link Promise} * is trusted, returns promiseOrValue, otherwise, returns a new, already-resolved
* whose resolution value is: * when.js promise whose resolution value is:
* * the resolution value of promiseOrValue if it's a foreign promise, or * * the resolution value of promiseOrValue if it's a foreign promise, or
* * promiseOrValue if it's a value * * promiseOrValue if it's a value
*/ */
function resolve(promiseOrValue) { function resolve(promiseOrValue) {
var promise, deferred; var promise;
if(promiseOrValue instanceof Promise) { if(promiseOrValue instanceof Promise) {
// It's a when.js promise, so we trust it // It's a when.js promise, so we trust it
promise = promiseOrValue; promise = promiseOrValue;
} else if(isPromise(promiseOrValue)) {
// Assimilate foreign promises
promise = assimilate(promiseOrValue);
} else { } else {
// It's not a when.js promise. See if it's a foreign promise or a value. // It's a value, create a fulfilled promise for it.
promise = fulfilled(promiseOrValue);
// Some promises, particularly Q promises, provide a valueOf method that
// attempts to synchronously return the fulfilled value of the promise, or
// returns the unresolved promise itself. Attempting to break a fulfillment
// value out of a promise appears to be necessary to break cycles between
// Q and When attempting to coerce each-other's promises in an infinite loop.
// For promises that do not implement "valueOf", the Object#valueOf is harmless.
// See: https://github.com/kriskowal/q/issues/106
// IMPORTANT: Must check for a promise here, since valueOf breaks other things
// like Date.
if (isPromise(promiseOrValue) && typeof promiseOrValue.valueOf === 'function') {
promiseOrValue = promiseOrValue.valueOf();
}
if(isPromise(promiseOrValue)) {
// It looks like a thenable, but we don't know where it came from,
// so we don't trust its implementation entirely. Introduce a trusted
// middleman when.js promise
deferred = defer();
// IMPORTANT: This is the only place when.js should ever call .then() on
// an untrusted promise.
promiseOrValue.then(deferred.resolve, deferred.reject, deferred.progress);
promise = deferred.promise;
} else {
// It's a value, not a promise. Create a resolved promise for it.
promise = fulfilled(promiseOrValue);
}
} }
return promise; return promise;
} }
/** /**
* Returns a rejected promise for the supplied promiseOrValue. If * Assimilate an untrusted thenable by introducing a trusted middle man.
* promiseOrValue is a value, it will be the rejection value of the * Not a perfect strategy, but possibly the best we can do.
* returned promise. If promiseOrValue is a promise, its * IMPORTANT: This is the only place when.js should ever call an untrusted
* completion value will be the rejected value of the returned promise * thenable's then() on an. Don't expose the return value to the untrusted thenable
* @memberOf when
* *
* @param promiseOrValue {*} the rejected value of the returned {@link Promise} * @param {*} thenable
* @param {function} thenable.then
* @returns {Promise}
*/
function assimilate(thenable) {
var d = defer();
// TODO: Enqueue this for future execution in 2.0
try {
thenable.then(
function(value) { d.resolve(value); },
function(reason) { d.reject(reason); },
function(update) { d.progress(update); }
);
} catch(e) {
d.reject(e);
}
return d.promise;
}
/**
* Returns a rejected promise for the supplied promiseOrValue. The returned
* promise will be rejected with:
* - promiseOrValue, if it is a value, or
* - if promiseOrValue is a promise
* - promiseOrValue's value after it is fulfilled
* - promiseOrValue's reason after it is rejected
* @param {*} promiseOrValue the rejected value of the returned {@link Promise}
* @return {Promise} rejected {@link Promise} * @return {Promise} rejected {@link Promise}
*/ */
function reject(promiseOrValue) { function reject(promiseOrValue) {
return when(promiseOrValue, function(value) { return when(promiseOrValue, rejected);
return rejected(value);
});
} }
/** /**
@ -311,25 +310,53 @@ define(['module'], function () {
Promise.prototype = { Promise.prototype = {
/** /**
* Register a callback that will be called when a promise is * Register a callback that will be called when a promise is
* resolved or rejected. Optionally also register a progress handler. * fulfilled or rejected. Optionally also register a progress handler.
* Shortcut for .then(alwaysback, alwaysback, progback) * Shortcut for .then(onFulfilledOrRejected, onFulfilledOrRejected, onProgress)
* @memberOf Promise * @param {function?} [onFulfilledOrRejected]
* @param alwaysback {Function} * @param {function?} [onProgress]
* @param progback {Function}
* @return {Promise} * @return {Promise}
*/ */
always: function(alwaysback, progback) { always: function(onFulfilledOrRejected, onProgress) {
return this.then(alwaysback, alwaysback, progback); return this.then(onFulfilledOrRejected, onFulfilledOrRejected, onProgress);
}, },
/** /**
* Register a rejection handler. Shortcut for .then(null, errback) * Register a rejection handler. Shortcut for .then(undefined, onRejected)
* @memberOf Promise * @param {function?} onRejected
* @param errback {Function}
* @return {Promise} * @return {Promise}
*/ */
otherwise: function(errback) { otherwise: function(onRejected) {
return this.then(undef, errback); return this.then(undef, onRejected);
},
/**
* Shortcut for .then(function() { return value; })
* @param {*} value
* @return {Promise} a promise that:
* - is fulfilled if value is not a promise, or
* - if value is a promise, will fulfill with its value, or reject
* with its reason.
*/
'yield': function(value) {
return this.then(function() {
return value;
});
},
/**
* Assumes that this promise will fulfill with an array, and arranges
* for the onFulfilled to be called with the array as its argument list
* i.e. onFulfilled.apply(undefined, array).
* @param {function} onFulfilled function to receive spread arguments
* @return {Promise}
*/
spread: function(onFulfilled) {
return this.then(function(array) {
// array may contain promises, so resolve its contents.
return all(array, function(array) {
return onFulfilled.apply(undef, array);
});
});
} }
}; };
@ -337,13 +364,13 @@ define(['module'], function () {
* Create an already-resolved promise for the supplied value * Create an already-resolved promise for the supplied value
* @private * @private
* *
* @param value anything * @param {*} value
* @return {Promise} * @return {Promise} fulfilled promise
*/ */
function fulfilled(value) { function fulfilled(value) {
var p = new Promise(function(callback) { var p = new Promise(function(onFulfilled) {
try { try {
return resolve(callback ? callback(value) : value); return resolve(typeof onFulfilled == 'function' ? onFulfilled(value) : value);
} catch(e) { } catch(e) {
return rejected(e); return rejected(e);
} }
@ -357,13 +384,13 @@ define(['module'], function () {
* rejection reason. * rejection reason.
* @private * @private
* *
* @param reason rejection reason * @param {*} reason
* @return {Promise} * @return {Promise} rejected promise
*/ */
function rejected(reason) { function rejected(reason) {
var p = new Promise(function(callback, errback) { var p = new Promise(function(_, onRejected) {
try { try {
return errback ? resolve(errback(reason)) : rejected(reason); return resolve(typeof onRejected == 'function' ? onRejected(reason) : rejected(reason));
} catch(e) { } catch(e) {
return rejected(e); return rejected(e);
} }
@ -378,14 +405,12 @@ define(['module'], function () {
* The Deferred itself has the full API: resolve, reject, progress, and * The Deferred itself has the full API: resolve, reject, progress, and
* then. The resolver has resolve, reject, and progress. The promise * then. The resolver has resolve, reject, and progress. The promise
* only has then. * only has then.
* @memberOf when
* @function
* *
* @return {Deferred} * @return {Deferred}
*/ */
function defer() { function defer() {
var deferred, promise, handlers, progressHandlers, var deferred, promise, handlers, progressHandlers,
_then, _progress, _resolve; _then, _notify, _resolve;
/** /**
* The promise for the new deferred * The promise for the new deferred
@ -399,18 +424,19 @@ define(['module'], function () {
* @name Deferred * @name Deferred
*/ */
deferred = { deferred = {
then: then, then: then, // DEPRECATED: use deferred.promise.then
resolve: promiseResolve, resolve: promiseResolve,
reject: promiseReject, reject: promiseReject,
// TODO: Consider renaming progress() to notify() progress: promiseNotify, // DEPRECATED: use deferred.notify
progress: promiseProgress, notify: promiseNotify,
promise: promise, promise: promise,
resolver: { resolver: {
resolve: promiseResolve, resolve: promiseResolve,
reject: promiseReject, reject: promiseReject,
progress: promiseProgress progress: promiseNotify, // DEPRECATED: use deferred.notify
notify: promiseNotify
} }
}; };
@ -422,29 +448,29 @@ define(['module'], function () {
* functions to the registered listeners * functions to the registered listeners
* @private * @private
* *
* @param [callback] {Function} resolution handler * @param {function?} [onFulfilled] resolution handler
* @param [errback] {Function} rejection handler * @param {function?} [onRejected] rejection handler
* @param [progback] {Function} progress handler * @param {function?} [onProgress] progress handler
* @throws {Error} if any argument is not null, undefined, or a Function
*/ */
_then = function(callback, errback, progback) { _then = function(onFulfilled, onRejected, onProgress) {
var deferred, progressHandler; var deferred, progressHandler;
deferred = defer(); deferred = defer();
progressHandler = progback
progressHandler = typeof onProgress === 'function'
? function(update) { ? function(update) {
try { try {
// Allow progress handler to transform progress event // Allow progress handler to transform progress event
deferred.progress(progback(update)); deferred.notify(onProgress(update));
} catch(e) { } catch(e) {
// Use caught value as progress // Use caught value as progress
deferred.progress(e); deferred.notify(e);
} }
} }
: deferred.progress; : function(update) { deferred.notify(update); };
handlers.push(function(promise) { handlers.push(function(promise) {
promise.then(callback, errback) promise.then(onFulfilled, onRejected)
.then(deferred.resolve, deferred.reject, progressHandler); .then(deferred.resolve, deferred.reject, progressHandler);
}); });
@ -456,9 +482,9 @@ define(['module'], function () {
/** /**
* Issue a progress event, notifying all progress listeners * Issue a progress event, notifying all progress listeners
* @private * @private
* @param update {*} progress event payload to pass to all listeners * @param {*} update progress event payload to pass to all listeners
*/ */
_progress = function(update) { _notify = function(update) {
processQueue(progressHandlers, update); processQueue(progressHandlers, update);
return update; return update;
}; };
@ -467,61 +493,58 @@ define(['module'], function () {
* Transition from pre-resolution state to post-resolution state, notifying * Transition from pre-resolution state to post-resolution state, notifying
* all listeners of the resolution or rejection * all listeners of the resolution or rejection
* @private * @private
* @param completed {Promise} the completed value of this deferred * @param {*} value the value of this deferred
*/ */
_resolve = function(completed) { _resolve = function(value) {
completed = resolve(completed);
// Replace _then with one that directly notifies with the result. // Replace _then with one that directly notifies with the result.
_then = completed.then; _then = value.then;
// Replace _resolve so that this Deferred can only be completed once // Replace _resolve so that this Deferred can only be resolved once
_resolve = resolve; _resolve = resolve;
// Make _progress a noop, to disallow progress for the resolved promise. // Make _progress a noop, to disallow progress for the resolved promise.
_progress = noop; _notify = identity;
// Notify handlers // Notify handlers
processQueue(handlers, completed); processQueue(handlers, value);
// Free progressHandlers array since we'll never issue progress events // Free progressHandlers array since we'll never issue progress events
progressHandlers = handlers = undef; progressHandlers = handlers = undef;
return completed; return value;
}; };
return deferred; return deferred;
/** /**
* Wrapper to allow _then to be replaced safely * Wrapper to allow _then to be replaced safely
* @param [callback] {Function} resolution handler * @param {function?} [onFulfilled] resolution handler
* @param [errback] {Function} rejection handler * @param {function?} [onRejected] rejection handler
* @param [progback] {Function} progress handler * @param {function?} [onProgress] progress handler
* @return {Promise} new Promise * @return {Promise} new promise
* @throws {Error} if any argument is not null, undefined, or a Function
*/ */
function then(callback, errback, progback) { function then(onFulfilled, onRejected, onProgress) {
return _then(callback, errback, progback); // TODO: Promises/A+ check typeof onFulfilled, onRejected, onProgress
return _then(onFulfilled, onRejected, onProgress);
} }
/** /**
* Wrapper to allow _resolve to be replaced * Wrapper to allow _resolve to be replaced
*/ */
function promiseResolve(val) { function promiseResolve(val) {
return _resolve(val); return _resolve(resolve(val));
} }
/** /**
* Wrapper to allow _resolve to be replaced * Wrapper to allow _reject to be replaced
*/ */
function promiseReject(err) { function promiseReject(err) {
return _resolve(rejected(err)); return _resolve(rejected(err));
} }
/** /**
* Wrapper to allow _progress to be replaced * Wrapper to allow _notify to be replaced
* @param {*} update progress update
*/ */
function promiseProgress(update) { function promiseNotify(update) {
return _progress(update); return _notify(update);
} }
} }
@ -531,7 +554,7 @@ define(['module'], function () {
* promiseOrValue is a promise. * promiseOrValue is a promise.
* *
* @param {*} promiseOrValue anything * @param {*} promiseOrValue anything
* @returns {Boolean} true if promiseOrValue is a {@link Promise} * @returns {boolean} true if promiseOrValue is a {@link Promise}
*/ */
function isPromise(promiseOrValue) { function isPromise(promiseOrValue) {
return promiseOrValue && typeof promiseOrValue.then === 'function'; return promiseOrValue && typeof promiseOrValue.then === 'function';
@ -542,25 +565,24 @@ define(['module'], function () {
* howMany of the supplied promisesOrValues have resolved, or will reject when * howMany of the supplied promisesOrValues have resolved, or will reject when
* it becomes impossible for howMany to resolve, for example, when * it becomes impossible for howMany to resolve, for example, when
* (promisesOrValues.length - howMany) + 1 input promises reject. * (promisesOrValues.length - howMany) + 1 input promises reject.
* @memberOf when
* *
* @param promisesOrValues {Array} array of anything, may contain a mix * @param {Array} promisesOrValues array of anything, may contain a mix
* of {@link Promise}s and values * of promises and values
* @param howMany {Number} number of promisesOrValues to resolve * @param howMany {number} number of promisesOrValues to resolve
* @param [callback] {Function} resolution handler * @param {function?} [onFulfilled] resolution handler
* @param [errback] {Function} rejection handler * @param {function?} [onRejected] rejection handler
* @param [progback] {Function} progress handler * @param {function?} [onProgress] progress handler
* @returns {Promise} promise that will resolve to an array of howMany values that * @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 * resolved first, or will reject with an array of (promisesOrValues.length - howMany) + 1
* rejection reasons. * rejection reasons.
*/ */
function some(promisesOrValues, howMany, callback, errback, progback) { function some(promisesOrValues, howMany, onFulfilled, onRejected, onProgress) {
checkCallbacks(2, arguments); checkCallbacks(2, arguments);
return when(promisesOrValues, function(promisesOrValues) { return when(promisesOrValues, function(promisesOrValues) {
var toResolve, toReject, values, reasons, deferred, fulfillOne, rejectOne, progress, len, i; var toResolve, toReject, values, reasons, deferred, fulfillOne, rejectOne, notify, len, i;
len = promisesOrValues.length >>> 0; len = promisesOrValues.length >>> 0;
@ -577,7 +599,7 @@ define(['module'], function () {
deferred.resolve(values); deferred.resolve(values);
} else { } else {
progress = deferred.progress; notify = deferred.notify;
rejectOne = function(reason) { rejectOne = function(reason) {
reasons.push(reason); reasons.push(reason);
@ -601,12 +623,12 @@ define(['module'], function () {
for(i = 0; i < len; ++i) { for(i = 0; i < len; ++i) {
if(i in promisesOrValues) { if(i in promisesOrValues) {
when(promisesOrValues[i], fulfiller, rejecter, progress); when(promisesOrValues[i], fulfiller, rejecter, notify);
} }
} }
} }
return deferred.then(callback, errback, progback); return deferred.promise.then(onFulfilled, onRejected, onProgress);
function rejecter(reason) { function rejecter(reason) {
rejectOne(reason); rejectOne(reason);
@ -623,23 +645,22 @@ define(['module'], function () {
* Initiates a competitive race, returning a promise that will resolve when * Initiates a competitive race, returning a promise that will resolve when
* any one of the supplied promisesOrValues has resolved or will reject when * any one of the supplied promisesOrValues has resolved or will reject when
* *all* promisesOrValues have rejected. * *all* promisesOrValues have rejected.
* @memberOf when
* *
* @param promisesOrValues {Array|Promise} array of anything, may contain a mix * @param {Array|Promise} promisesOrValues array of anything, may contain a mix
* of {@link Promise}s and values * of {@link Promise}s and values
* @param [callback] {Function} resolution handler * @param {function?} [onFulfilled] resolution handler
* @param [errback] {Function} rejection handler * @param {function?} [onRejected] rejection handler
* @param [progback] {Function} progress handler * @param {function?} [onProgress] progress handler
* @returns {Promise} promise that will resolve to the value that resolved first, or * @returns {Promise} promise that will resolve to the value that resolved first, or
* will reject with an array of all rejected inputs. * will reject with an array of all rejected inputs.
*/ */
function any(promisesOrValues, callback, errback, progback) { function any(promisesOrValues, onFulfilled, onRejected, onProgress) {
function unwrapSingleResult(val) { function unwrapSingleResult(val) {
return callback ? callback(val[0]) : val[0]; return onFulfilled ? onFulfilled(val[0]) : val[0];
} }
return some(promisesOrValues, 1, unwrapSingleResult, errback, progback); return some(promisesOrValues, 1, unwrapSingleResult, onRejected, onProgress);
} }
/** /**
@ -648,22 +669,20 @@ define(['module'], function () {
* containing the resolution values of each of the promisesOrValues. * containing the resolution values of each of the promisesOrValues.
* @memberOf when * @memberOf when
* *
* @param promisesOrValues {Array|Promise} array of anything, may contain a mix * @param {Array|Promise} promisesOrValues array of anything, may contain a mix
* of {@link Promise}s and values * of {@link Promise}s and values
* @param [callback] {Function} * @param {function?} [onFulfilled] resolution handler
* @param [errback] {Function} * @param {function?} [onRejected] rejection handler
* @param [progressHandler] {Function} * @param {function?} [onProgress] progress handler
* @returns {Promise} * @returns {Promise}
*/ */
function all(promisesOrValues, callback, errback, progressHandler) { function all(promisesOrValues, onFulfilled, onRejected, onProgress) {
checkCallbacks(1, arguments); checkCallbacks(1, arguments);
return map(promisesOrValues, identity).then(callback, errback, progressHandler); return map(promisesOrValues, identity).then(onFulfilled, onRejected, onProgress);
} }
/** /**
* Joins multiple promises into a single returned promise. * Joins multiple promises into a single returned promise.
* @memberOf when
* @param {Promise|*} [...promises] two or more promises to join
* @return {Promise} a promise that will fulfill when *all* the input promises * @return {Promise} a promise that will fulfill when *all* the input promises
* have fulfilled, or will reject when *any one* of the input promises rejects. * have fulfilled, or will reject when *any one* of the input promises rejects.
*/ */
@ -676,18 +695,16 @@ define(['module'], function () {
* input to contain {@link Promise}s and/or values, and mapFunc may return * input to contain {@link Promise}s and/or values, and mapFunc may return
* either a value or a {@link Promise} * either a value or a {@link Promise}
* *
* @memberOf when * @param {Array|Promise} promise array of anything, may contain a mix
*
* @param promise {Array|Promise} array of anything, may contain a mix
* of {@link Promise}s and values * of {@link Promise}s and values
* @param mapFunc {Function} mapping function mapFunc(value) which may return * @param {function} mapFunc mapping function mapFunc(value) which may return
* either a {@link Promise} or value * either a {@link Promise} or value
* @returns {Promise} a {@link Promise} that will resolve to an array containing * @returns {Promise} a {@link Promise} that will resolve to an array containing
* the mapped output values. * the mapped output values.
*/ */
function map(promise, mapFunc) { function map(promise, mapFunc) {
return when(promise, function(array) { return when(promise, function(array) {
var results, len, toResolve, resolve, reject, i, d; var results, len, toResolve, resolve, i, d;
// Since we know the resulting length, we can preallocate the results // Since we know the resulting length, we can preallocate the results
// array to avoid array expansions. // array to avoid array expansions.
@ -699,7 +716,6 @@ define(['module'], function () {
d.resolve(results); d.resolve(results);
} else { } else {
reject = d.reject;
resolve = function resolveOne(item, i) { resolve = function resolveOne(item, i) {
when(item, mapFunc).then(function(mapped) { when(item, mapFunc).then(function(mapped) {
results[i] = mapped; results[i] = mapped;
@ -707,7 +723,7 @@ define(['module'], function () {
if(!--toResolve) { if(!--toResolve) {
d.resolve(results); d.resolve(results);
} }
}, reject); }, d.reject);
}; };
// Since mapFunc may be async, get all invocations of it into flight // Since mapFunc may be async, get all invocations of it into flight
@ -728,18 +744,15 @@ define(['module'], function () {
/** /**
* Traditional reduce function, similar to `Array.prototype.reduce()`, but * Traditional reduce function, similar to `Array.prototype.reduce()`, but
* input may contain {@link Promise}s and/or values, and reduceFunc * input may contain promises and/or values, and reduceFunc
* may return either a value or a {@link Promise}, *and* initialValue may * may return either a value or a promise, *and* initialValue may
* be a {@link Promise} for the starting value. * be a promise for the starting value.
* @memberOf when
* *
* @param promise {Array|Promise} array of anything, may contain a mix * @param {Array|Promise} promise array or promise for an array of anything,
* of {@link Promise}s and values. May also be a {@link Promise} for * may contain a mix of promises and values.
* an array. * @param {function} reduceFunc reduce function reduce(currentValue, nextValue, index, total),
* @param reduceFunc {Function} reduce function reduce(currentValue, nextValue, index, total),
* where total is the total number of items being reduced, and will be the same * where total is the total number of items being reduced, and will be the same
* in each call to reduceFunc. * in each call to reduceFunc.
* @param [initialValue] {*} starting value, or a {@link Promise} for the starting value
* @returns {Promise} that will resolve to the final reduced value * @returns {Promise} that will resolve to the final reduced value
*/ */
function reduce(promise, reduceFunc /*, initialValue */) { function reduce(promise, reduceFunc /*, initialValue */) {
@ -765,13 +778,14 @@ define(['module'], function () {
} }
/** /**
* Ensure that resolution of promiseOrValue will complete resolver with the completion * Ensure that resolution of promiseOrValue will trigger resolver with the
* value of promiseOrValue, or instead with resolveValue if it is provided. * value or reason of promiseOrValue, or instead with resolveValue if it is provided.
* @memberOf when
* *
* @param promiseOrValue * @param promiseOrValue
* @param resolver {Resolver} * @param {Object} resolver
* @param [resolveValue] anything * @param {function} resolver.resolve
* @param {function} resolver.reject
* @param {*} [resolveValue]
* @returns {Promise} * @returns {Promise}
*/ */
function chain(promiseOrValue, resolver, resolveValue) { function chain(promiseOrValue, resolver, resolveValue) {
@ -779,10 +793,18 @@ define(['module'], function () {
return when(promiseOrValue, return when(promiseOrValue,
function(val) { function(val) {
return resolver.resolve(useResolveValue ? resolveValue : val); val = useResolveValue ? resolveValue : val;
resolver.resolve(val);
return val;
}, },
resolver.reject, function(reason) {
resolver.progress resolver.reject(reason);
return rejected(reason);
},
function(update) {
typeof resolver.notify === 'function' && resolver.notify(update);
return update;
}
); );
} }
@ -790,6 +812,11 @@ define(['module'], function () {
// Utility functions // Utility functions
// //
/**
* Apply all functions in queue to value
* @param {Array} queue array of functions to execute
* @param {*} value argument passed to each function
*/
function processQueue(queue, value) { function processQueue(queue, value) {
var handler, i = 0; var handler, i = 0;
@ -802,12 +829,13 @@ define(['module'], function () {
* Helper that checks arrayOfCallbacks to ensure that each element is either * Helper that checks arrayOfCallbacks to ensure that each element is either
* a function, or null or undefined. * a function, or null or undefined.
* @private * @private
* * @param {number} start index at which to start checking items in arrayOfCallbacks
* @param arrayOfCallbacks {Array} array to check * @param {Array} arrayOfCallbacks array to check
* @throws {Error} if any element of arrayOfCallbacks is something other than * @throws {Error} if any element of arrayOfCallbacks is something other than
* a Functions, null, or undefined. * a functions, null, or undefined.
*/ */
function checkCallbacks(start, arrayOfCallbacks) { function checkCallbacks(start, arrayOfCallbacks) {
// TODO: Promises/A+ update type checking and docs
var arg, i = arrayOfCallbacks.length; var arg, i = arrayOfCallbacks.length;
while(i > start) { while(i > start) {
@ -889,7 +917,7 @@ define(['module'], function () {
}); });
})(typeof define == 'function' && define.amd })(typeof define == 'function' && define.amd
? define ? define
: function (deps, factory) { typeof exports === 'object' : function (factory) { typeof exports === 'object'
? (module.exports = factory()) ? (module.exports = factory())
: (this.when = factory()); : (this.when = factory());
} }

File diff suppressed because one or more lines are too long