diff --git a/src/ng/q.js b/src/ng/q.js index 5b4ecf5a262c..609dd1f51480 100644 --- a/src/ng/q.js +++ b/src/ng/q.js @@ -636,6 +636,30 @@ function qFactory(nextTick, exceptionHandler, errorOnUnhandledRejections) { return deferred.promise; } + /** + * @ngdoc method + * @name $q#race + * @kind function + * + * @description + * Returns a promise that is resolved similarly as the first promise to resolve. + * + * @param {Array.|Object.} promises An array or hash of promises. + * @returns {Promise} Returns a promise that will be resolved as soon as the first promise in + * `promises` is resolved. The resolved value will be the same as the value of the first + * resolved promise. + */ + + function race(promises) { + var deferred = defer(); + + forEach(promises, function(promise) { + when(promise).then(deferred.resolve, deferred.reject); + }); + + return deferred.promise; + } + var $Q = function Q(resolver) { if (!isFunction(resolver)) { throw $qMinErr('norslvr', "Expected resolverFn, got '{0}'", resolver); @@ -665,6 +689,7 @@ function qFactory(nextTick, exceptionHandler, errorOnUnhandledRejections) { $Q.when = when; $Q.resolve = resolve; $Q.all = all; + $Q.race = race; return $Q; } diff --git a/test/ng/qSpec.js b/test/ng/qSpec.js index aafbb45f87ab..2d0325464c39 100644 --- a/test/ng/qSpec.js +++ b/test/ng/qSpec.js @@ -1986,6 +1986,78 @@ describe('q', function() { }); }); + describe('race (array)', function() { + it('should do nothing if given an empty array', function() { + q.race([]).then(success(), error()); + expect(mockNextTick.queue.length).toBe(0); + expect(logStr()).toBe(''); + }); + + it('should resolve as soon as the first promise is settled by resolution', function() { + var deferred1 = defer(), + deferred2 = defer(); + + q.race([promise, deferred1.promise, deferred2.promise]).then(success(), error()); + expect(logStr()).toBe(''); + syncResolve(deferred1, 'hi'); + expect(logStr()).toBe('success(hi)->hi'); + syncResolve(deferred2, 'cau'); + expect(logStr()).toBe('success(hi)->hi'); + syncReject(deferred, 'hola'); + expect(logStr()).toBe('success(hi)->hi'); + }); + + it('should reject as soon as the first promise is settled by rejection', function() { + var deferred1 = defer(), + deferred2 = defer(); + + q.race([promise, deferred1.promise, deferred2.promise]).then(success(), error()); + expect(logStr()).toBe(''); + syncReject(deferred1, 'hi'); + expect(logStr()).toBe('error(hi)->reject(hi)'); + syncResolve(deferred2, 'cau'); + expect(logStr()).toBe('error(hi)->reject(hi)'); + syncReject(deferred, 'hola'); + expect(logStr()).toBe('error(hi)->reject(hi)'); + }); + }); + + describe('race (hash)', function() { + it('should do nothing if given an empty array', function() { + q.race({}).then(success(), error()); + expect(mockNextTick.queue.length).toBe(0); + expect(logStr()).toBe(''); + }); + + it('should resolve as soon as the first promise is settled by resolution', function() { + var deferred1 = defer(), + deferred2 = defer(); + + q.race({a: promise, b: deferred1.promise, c: deferred2.promise}).then(success(), error()); + expect(logStr()).toBe(''); + syncResolve(deferred1, 'hi'); + expect(logStr()).toBe('success(hi)->hi'); + syncResolve(deferred2, 'cau'); + expect(logStr()).toBe('success(hi)->hi'); + syncReject(deferred, 'hola'); + expect(logStr()).toBe('success(hi)->hi'); + }); + + it('should reject as soon as the first promise is settled by rejection', function() { + var deferred1 = defer(), + deferred2 = defer(); + + q.race({a: promise, b: deferred1.promise, c: deferred2.promise}).then(success(), error()); + expect(logStr()).toBe(''); + syncReject(deferred1, 'hi'); + expect(logStr()).toBe('error(hi)->reject(hi)'); + syncResolve(deferred2, 'cau'); + expect(logStr()).toBe('error(hi)->reject(hi)'); + syncReject(deferred, 'hola'); + expect(logStr()).toBe('error(hi)->reject(hi)'); + }); + }); + describe('exception logging', function() { var mockExceptionLogger = { log: [],