From fac6030748a6d5dc138760a9f521ec05f397236b Mon Sep 17 00:00:00 2001 From: Yihang Ho Date: Mon, 13 Jun 2016 18:29:21 +0800 Subject: [PATCH 1/3] feat($q): implement $q.race Implement $q.race. $q.race takes in an array or hash of promises and returns a promsie that is resolved as soon as the first promise is resolved. Closes #12929 --- src/ng/q.js | 29 +++++++++++++++++++++++++++++ test/ng/qSpec.js | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/src/ng/q.js b/src/ng/q.js index 5b4ecf5a262c..c227ee644c9d 100644 --- a/src/ng/q.js +++ b/src/ng/q.js @@ -636,6 +636,34 @@ 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 = new Deferred(); + + forEach(promises, function(promise) { + when(promise).then(function(value) { + deferred.resolve(value); + }, function(reason) { + deferred.reject(reason); + }); + }); + + return deferred.promise; + } + var $Q = function Q(resolver) { if (!isFunction(resolver)) { throw $qMinErr('norslvr', "Expected resolverFn, got '{0}'", resolver); @@ -665,6 +693,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..ea4fd30cfd10 100644 --- a/test/ng/qSpec.js +++ b/test/ng/qSpec.js @@ -1986,6 +1986,42 @@ describe('q', function() { }); }); + describe('race', 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('exception logging', function() { var mockExceptionLogger = { log: [], From 1bf49e3f0a14522cd55d02e85c3a7a286bf731a0 Mon Sep 17 00:00:00 2001 From: Yihang Ho Date: Wed, 15 Jun 2016 21:24:02 +0800 Subject: [PATCH 2/3] test($q): add test for $q.race taking in objects --- test/ng/qSpec.js | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/test/ng/qSpec.js b/test/ng/qSpec.js index ea4fd30cfd10..2d0325464c39 100644 --- a/test/ng/qSpec.js +++ b/test/ng/qSpec.js @@ -1986,7 +1986,7 @@ describe('q', function() { }); }); - describe('race', 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); @@ -2022,6 +2022,42 @@ describe('q', function() { }); }); + 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: [], From dc2be344ca1e9d623c3b83c3c94adc33f7c19a2d Mon Sep 17 00:00:00 2001 From: Yihang Ho Date: Thu, 16 Jun 2016 10:55:00 +0800 Subject: [PATCH 3/3] refactor($q): Simplify $q.race --- src/ng/q.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/ng/q.js b/src/ng/q.js index c227ee644c9d..609dd1f51480 100644 --- a/src/ng/q.js +++ b/src/ng/q.js @@ -651,14 +651,10 @@ function qFactory(nextTick, exceptionHandler, errorOnUnhandledRejections) { */ function race(promises) { - var deferred = new Deferred(); + var deferred = defer(); forEach(promises, function(promise) { - when(promise).then(function(value) { - deferred.resolve(value); - }, function(reason) { - deferred.reject(reason); - }); + when(promise).then(deferred.resolve, deferred.reject); }); return deferred.promise;