Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit e27bb6e

Browse files
ashtuchkinIgorMinar
authored andcommitted
feat($q): $q.all() now accepts hash
When waiting for several promises at once, it is often desirable to have them by name, not just by index in array. Example of this kind of interface already implemented would be a $routeProvider.when(url, {resolve: <hash of promises>}), where resources/promises are given by names, and then results accessed by names in controller.
1 parent 7b236b2 commit e27bb6e

File tree

2 files changed

+85
-18
lines changed

2 files changed

+85
-18
lines changed

src/ng/q.js

+18-17
Original file line numberDiff line numberDiff line change
@@ -377,29 +377,30 @@ function qFactory(nextTick, exceptionHandler) {
377377
* Combines multiple promises into a single promise that is resolved when all of the input
378378
* promises are resolved.
379379
*
380-
* @param {Array.<Promise>} promises An array of promises.
381-
* @returns {Promise} Returns a single promise that will be resolved with an array of values,
382-
* each value corresponding to the promise at the same index in the `promises` array. If any of
380+
* @param {Array.<Promise>|Object.<Promise>} promises An array or hash of promises.
381+
* @returns {Promise} Returns a single promise that will be resolved with an array/hash of values,
382+
* each value corresponding to the promise at the same index/key in the `promises` array/hash. If any of
383383
* the promises is resolved with a rejection, this resulting promise will be resolved with the
384384
* same rejection.
385385
*/
386386
function all(promises) {
387387
var deferred = defer(),
388-
counter = promises.length,
389-
results = [];
390-
391-
if (counter) {
392-
forEach(promises, function(promise, index) {
393-
ref(promise).then(function(value) {
394-
if (index in results) return;
395-
results[index] = value;
396-
if (!(--counter)) deferred.resolve(results);
397-
}, function(reason) {
398-
if (index in results) return;
399-
deferred.reject(reason);
400-
});
388+
counter = 0,
389+
results = isArray(promises) ? [] : {};
390+
391+
forEach(promises, function(promise, key) {
392+
counter++;
393+
ref(promise).then(function(value) {
394+
if (results.hasOwnProperty(key)) return;
395+
results[key] = value;
396+
if (!(--counter)) deferred.resolve(results);
397+
}, function(reason) {
398+
if (results.hasOwnProperty(key)) return;
399+
deferred.reject(reason);
401400
});
402-
} else {
401+
});
402+
403+
if (counter === 0) {
403404
deferred.resolve(results);
404405
}
405406

test/ng/qSpec.js

+67-1
Original file line numberDiff line numberDiff line change
@@ -683,7 +683,7 @@ describe('q', function() {
683683
});
684684

685685

686-
describe('all', function() {
686+
describe('all (array)', function() {
687687
it('should resolve all of nothing', function() {
688688
var result;
689689
q.all([]).then(function(r) { result = r; });
@@ -742,6 +742,72 @@ describe('q', function() {
742742
});
743743
});
744744

745+
describe('all (hash)', function() {
746+
it('should resolve all of nothing', function() {
747+
var result;
748+
q.all({}).then(function(r) { result = r; });
749+
mockNextTick.flush();
750+
expect(result).toEqual({});
751+
});
752+
753+
754+
it('should take a hash of promises and return a promise for a hash of results', function() {
755+
var deferred1 = defer(),
756+
deferred2 = defer();
757+
758+
q.all({en: promise, fr: deferred1.promise, es: deferred2.promise}).then(success(), error());
759+
expect(logStr()).toBe('');
760+
syncResolve(deferred, 'hi');
761+
expect(logStr()).toBe('');
762+
syncResolve(deferred2, 'hola');
763+
expect(logStr()).toBe('');
764+
syncResolve(deferred1, 'salut');
765+
expect(logStr()).toBe('success({en:hi,es:hola,fr:salut})');
766+
});
767+
768+
769+
it('should reject the derived promise if at least one of the promises in the hash is rejected',
770+
function() {
771+
var deferred1 = defer(),
772+
deferred2 = defer();
773+
774+
q.all({en: promise, fr: deferred1.promise, es: deferred2.promise}).then(success(), error());
775+
expect(logStr()).toBe('');
776+
syncResolve(deferred2, 'hola');
777+
expect(logStr()).toBe('');
778+
syncReject(deferred1, 'oops');
779+
expect(logStr()).toBe('error(oops)');
780+
});
781+
782+
783+
it('should ignore multiple resolutions of an (evil) hash promise', function() {
784+
var evilPromise = {
785+
then: function(success, error) {
786+
evilPromise.success = success;
787+
evilPromise.error = error;
788+
}
789+
}
790+
791+
q.all({good: promise, evil: evilPromise}).then(success(), error());
792+
expect(logStr()).toBe('');
793+
794+
evilPromise.success('first');
795+
evilPromise.success('muhaha');
796+
evilPromise.error('arghhh');
797+
expect(logStr()).toBe('');
798+
799+
syncResolve(deferred, 'done');
800+
expect(logStr()).toBe('success({evil:first,good:done})');
801+
});
802+
803+
it('should handle correctly situation when given the same promise several times', function() {
804+
q.all({first: promise, second: promise, third: promise}).then(success(), error());
805+
expect(logStr()).toBe('');
806+
807+
syncResolve(deferred, 'done');
808+
expect(logStr()).toBe('success({first:done,second:done,third:done})');
809+
});
810+
});
745811

746812
describe('exception logging', function() {
747813
var mockExceptionLogger = {

0 commit comments

Comments
 (0)