Skip to content

Commit

Permalink
See #60. Refactor some() to initiate a competitive race that only rej…
Browse files Browse the repository at this point in the history
…ects once it becomes impossible to resolve howMany inputs
  • Loading branch information
briancavalier committed Oct 26, 2012
1 parent 2ab5710 commit da394fa
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 54 deletions.
6 changes: 3 additions & 3 deletions test/any.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,12 @@ buster.testCase('when.any', {
).always(done);
},

'should reject with a rejected input value': function(done) {
var input = [rejected(1), resolved(2), resolved(3)];
'should reject with all rejected input values if all inputs are rejected': function(done) {
var input = [rejected(1), rejected(2), rejected(3)];
when.any(input,
fail,
function(result) {
assert.equals(result, 1);
assert.equals(result, [1, 2, 3]);
}
).always(done);
},
Expand Down
6 changes: 3 additions & 3 deletions test/some.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,12 @@ buster.testCase('when.some', {
).always(done);
},

'should reject if any input promise rejects before desired number of inputs are resolved': function(done) {
var input = [resolved(1), rejected(2), resolved(3)];
'should reject with a all rejected input values if resolving howMany becomes impossible': function(done) {
var input = [resolved(1), rejected(2), rejected(3)];
when.some(input, 2,
fail,
function(failed) {
assert.equals(failed, 2);
assert.equals(failed, [2, 3]);
}
).always(done);
},
Expand Down
114 changes: 66 additions & 48 deletions when.js
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@ define(['module'], function () {

/**
* Wrapper to allow _progress to be replaced
* @param {*} update progress update
* @param {*} update progress update
*/
function promiseProgress(update) {
return _progress(update);
Expand All @@ -372,64 +372,72 @@ define(['module'], function () {
* test from http://wiki.commonjs.org/wiki/Promises/A to determine if
* promiseOrValue is a promise.
*
* @param promiseOrValue anything
* @param {*} promiseOrValue anything
* @returns {Boolean} true if promiseOrValue is a {@link Promise}
*/
function isPromise(promiseOrValue) {
return promiseOrValue && typeof promiseOrValue.then === 'function';
}

/**
* Return a promise that will resolve when howMany of the supplied promisesOrValues
* have resolved. The resolution value of the returned promise will be an array of
* length howMany containing the resolutions values of the triggering promisesOrValues.
* Initiates a competitive race, returning a promise that will resolve when
* howMany of the supplied promisesOrValues have resolved, or will reject when
* it becomes impossible for howMany to resolve, for example, when
* (promisesOrValues.length - howMany) + 1 input promises reject.
* @memberOf when
*
* @param promisesOrValues {Array} array of anything, may contain a mix
* of {@link Promise}s and values
* @param howMany
* @param [callback]
* @param [errback]
* @param [progressHandler]
* @returns {Promise}
* @param howMany {Number} number of promisesOrValues to resolve
* @param [callback] {Function} resolution handler
* @param [errback] {Function} rejection handler
* @param [progback] {Function} progress handler
* @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, callback, errback, progressHandler) {
function some(promisesOrValues, howMany, callback, errback, progback) {

checkCallbacks(2, arguments);

return when(promisesOrValues, function(promisesOrValues) {

var toResolve, results, deferred, resolve, reject, progress, len, i;
var toResolve, toReject, values, reasons, deferred, resolveOne, rejectOne, progress, len, i;

len = promisesOrValues.length >>> 0;

toResolve = Math.max(0, Math.min(howMany, len));
results = [];
values = [];

toReject = (len - toResolve) + 1;
reasons = [];

deferred = defer();

// No items in the input, resolve immediately
if (!toResolve) {
deferred.resolve(results);
deferred.resolve(values);

} else {
// TODO: Consider rejecting only when N (or promises.length - N?)
// promises have been rejected instead of only one?
reject = deferred.reject;
progress = deferred.progress;

// Resolver for promises. Captures the value and resolves
// the returned promise when toResolve reaches zero.
// Overwrites resolver var with a noop once promise has
// be resolved to cover case where n < promises.length
resolve = function(val) {
rejectOne = function(reason) {
reasons.push(reason);
if(!--toReject) {
resolveOne = rejectOne = noop;
deferred.reject(reasons);
}
};

resolveOne = function(val) {
// This orders the values based on promise resolution order
// Another strategy would be to use the original position of
// the corresponding promise.
results.push(val);
values.push(val);

if (!--toResolve) {
resolve = noop;
deferred.resolve(results);
resolveOne = rejectOne = noop;
deferred.resolve(values);
}
};

Expand All @@ -440,10 +448,42 @@ define(['module'], function () {
}
}

return deferred.then(callback, errback, progressHandler);
return deferred.then(callback, errback, progback);

function reject(reason) {
rejectOne(reason);
}

function resolve(val) {
resolveOne(val);
}

});
}

/**
* Initiates a competitive race, returning a promise that will resolve when
* any one of the supplied promisesOrValues has resolved or will reject when
* *all* promisesOrValues have rejected.
* @memberOf when
*
* @param promisesOrValues {Array|Promise} array of anything, may contain a mix
* of {@link Promise}s and values
* @param [callback] {Function} resolution handler
* @param [errback] {Function} rejection handler
* @param [progback] {Function} progress handler
* @returns {Promise} promise that will resolve to the value that resolved first, or
* will reject with an array of all rejected inputs.
*/
function any(promisesOrValues, callback, errback, progback) {

function unwrapSingleResult(val) {
return callback ? callback(val[0]) : val[0];
}

return some(promisesOrValues, 1, unwrapSingleResult, errback, progback);
}

/**
* Return a promise that will resolve only once all the supplied promisesOrValues
* have resolved. The resolution value of the returned promise will be an array
Expand Down Expand Up @@ -473,28 +513,6 @@ define(['module'], function () {
return map(arguments, identity);
}

/**
* Return a promise that will resolve when any one of the supplied promisesOrValues
* has resolved. The resolution value of the returned promise will be the resolution
* value of the triggering promiseOrValue.
* @memberOf when
*
* @param promisesOrValues {Array|Promise} array of anything, may contain a mix
* of {@link Promise}s and values
* @param [callback] {Function}
* @param [errback] {Function}
* @param [progressHandler] {Function}
* @returns {Promise}
*/
function any(promisesOrValues, callback, errback, progressHandler) {

function unwrapSingleResult(val) {
return callback ? callback(val[0]) : val[0];
}

return some(promisesOrValues, 1, unwrapSingleResult, errback, progressHandler);
}

/**
* Traditional map function, similar to `Array.prototype.map()`, but allows
* input to contain {@link Promise}s and/or values, and mapFunc may return
Expand Down

0 comments on commit da394fa

Please sign in to comment.