Skip to content

Commit

Permalink
Merge pull request #113 from decasia/all-settled
Browse files Browse the repository at this point in the history
Add allSettled to RSVP
  • Loading branch information
stefanpenner committed Jan 4, 2014
2 parents 60aafad + fa0007c commit e94e4cd
Show file tree
Hide file tree
Showing 3 changed files with 199 additions and 3 deletions.
3 changes: 2 additions & 1 deletion lib/rsvp.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Promise from "./rsvp/promise";
import EventTarget from "./rsvp/events";
import denodeify from "./rsvp/node";
import all from "./rsvp/all";
import allSettled from "./rsvp/all_settled";
import race from "./rsvp/race";
import hash from "./rsvp/hash";
import rethrow from "./rsvp/rethrow";
Expand Down Expand Up @@ -38,4 +39,4 @@ if (typeof window !== 'undefined' && typeof window.__PROMISE_INSTRUMENTATION__ =
}
}

export { Promise, EventTarget, all, race, hash, rethrow, defer, denodeify, configure, on, off, resolve, reject, async, map, filter };
export { Promise, EventTarget, all, allSettled, race, hash, rethrow, defer, denodeify, configure, on, off, resolve, reject, async, map, filter };
106 changes: 106 additions & 0 deletions lib/rsvp/all_settled.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import Promise from "./promise";
import { isArray, isNonThenable } from "./utils";

/**
`RSVP.allSettled` is similar to `RSVP.all`, but instead of implementing
a fail-fast method, it waits until all the promises have returned and
shows you all the results. This is useful if you want to handle multiple
promises' failure states together as a set.
Returns a promise that is fulfilled when all the given promises have been
settled. The return promise is fulfilled with an array of the states of
the promises passed into the `promises` array argument.
Each state object will either indicate fulfillment or rejection, and
provide the corresponding value or reason. The states will take one of
the following formats:
```javascript
{ state: "fulfilled", value: value }
or
{ state: "rejected", reason: reason }
```
Example:
```javascript
var promise1 = RSVP.resolve(1);
var promise2 = RSVP.reject(new Error("2"));
var promise3 = RSVP.reject(new Error("3"));
var promises = [ promise1, promise2, promise3 ];
RSVP.allSettled(promises).then(function(array){
// array == [
// { state: "fulfilled", value: 1 },
// { state: "rejected", reason: Error },
// { state: "rejected", reason: Error }
// ]
// Note that for the second item, reason.message will be "2", and for the
// third item, reason.message will be "3".
}, function(error) {
// Not run. (This block would only be called if allSettled had failed,
// for instance if passed an incorrect argument type.)
});
```
@method @allSettled
@for RSVP
@param {Array} promises;
@param {String} label - optional string that describes the promise.
Useful for tooling.
@return {Promise} promise that is fulfilled with an array of the settled
states of the constituent promises.
*/

export default function allSettled(entries, label) {
return new Promise(function(resolve, reject) {
if (!isArray(entries)) {
throw new TypeError('You must pass an array to allSettled.');
}

var remaining = entries.length;
var results = new Array(remaining);
var entry;

if (remaining === 0) {
resolve([]);
}

function fulfilledState(value) {
return { state: "fulfilled", value: value };
}

function rejectedState(reason) {
return { state: "rejected", reason: reason };
}

function fulfilledResolver(index) {
return function(value) {
resolveAll(index, fulfilledState(value) );
};
}

function rejectedResolver(index) {
return function(reason) {
resolveAll(index, rejectedState(reason) );
};
}

function resolveAll(index, value) {
results[index] = value;
if (--remaining === 0) {
resolve(results);
}
}

for (var index = 0; index < entries.length; index++) {
entry = entries[index];

if (isNonThenable(entry)) {
resolveAll(index, fulfilledState(entry) );
} else {
Promise.cast(entry).then( fulfilledResolver(index), rejectedResolver(index) );
}
}
}, label);
};
93 changes: 91 additions & 2 deletions test/tests/extension_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -730,6 +730,95 @@ describe("RSVP extensions", function() {
});
}

describe("RSVP.allSettled", function() {
it('should exist', function() {
assert(RSVP.allSettled);
});

it('throws when not passed an array', function() {
var nothing = assertRejection(RSVP.allSettled());
var string = assertRejection(RSVP.allSettled(''));
var object = assertRejection(RSVP.allSettled({}));

RSVP.Promise.all([
nothing,
string,
object
]).then(function(){ done(); });
});

specify('resolves an empty array passed to allSettled()', function(done) {
RSVP.allSettled([]).then(function(results) {
assert(results.length === 0);
done();
});
});

specify('works with promises, thenables, non-promises and rejected promises',
function(done) {
var promise = new RSVP.Promise(function(resolve) { resolve(1); });
var syncThenable = { then: function (onFulfilled) { onFulfilled(2); } };
var asyncThenable = {
then: function (onFulfilled) {
setTimeout(function() { onFulfilled(3); }, 0);
}
};
var nonPromise = 4;
var rejectedPromise = new RSVP.Promise(function(resolve, reject) {
reject(new Error('WHOOPS'));
});


var entries = new Array(
promise, syncThenable, asyncThenable, nonPromise, rejectedPromise
);

RSVP.allSettled(entries).then(function(results) {
assert(objectEquals(results[0], {state: "fulfilled", value: 1} ));
assert(objectEquals(results[1], {state: "fulfilled", value: 2} ));
assert(objectEquals(results[2], {state: "fulfilled", value: 3} ));
assert(objectEquals(results[3], {state: "fulfilled", value: 4} ));
assert(results[4].state, "rejected");
assert(results[4].reason.message, "WHOOPS");
done();
});
}
);

specify('fulfilled only after all of the other promises are fulfilled', function(done) {
var firstResolved, secondResolved, firstResolver, secondResolver;

var first = new RSVP.Promise(function(resolve) {
firstResolver = resolve;
});
first.then(function() {
firstResolved = true;
});

var second = new RSVP.Promise(function(resolve) {
secondResolver = resolve;
});
second.then(function() {
secondResolved = true;
});

setTimeout(function() {
firstResolver(true);
}, 0);

setTimeout(function() {
secondResolver(true);
}, 0);

RSVP.allSettled([first, second]).then(function() {
assert(firstResolved);
assert(secondResolved);
done();
});
});

});

describe("RSVP.reject", function(){
specify("it should exist", function(){
assert(RSVP.reject);
Expand Down Expand Up @@ -1349,7 +1438,7 @@ describe("RSVP extensions", function() {
return {
key: matches[1],
index: parseInt(matches[2], 10)
}
}
} else {
throw new Error('unknown guid:' + guid);
}
Expand Down Expand Up @@ -1501,7 +1590,7 @@ describe("RSVP extensions", function() {

var values, originalAsync;
beforeEach(function() {
originalAsync = RSVP.configure('async');
originalAsync = RSVP.configure('async');
values = [];
});

Expand Down

0 comments on commit e94e4cd

Please sign in to comment.