From 7b4403fea6f59e42570c2b3d731400bc28db9281 Mon Sep 17 00:00:00 2001 From: Caitlin Potter Date: Thu, 26 Jun 2014 11:38:04 -0400 Subject: [PATCH] fix($timeout/$interval): if invokeApply is false, do not use evalAsync $evalAsync triggers a digest, and is unsuitable when it is expected that a digest should not occur. --- src/AngularPublic.js | 2 ++ src/ng/interval.js | 10 +++++----- src/ng/q.js | 7 +++++++ src/ng/timeout.js | 8 ++++---- test/ng/intervalSpec.js | 17 +++++++++++++++++ test/ng/timeoutSpec.js | 16 ++++++++++++++++ 6 files changed, 51 insertions(+), 9 deletions(-) diff --git a/src/AngularPublic.js b/src/AngularPublic.js index 4f2474fba9f7..3e741e8ed2e1 100644 --- a/src/AngularPublic.js +++ b/src/AngularPublic.js @@ -73,6 +73,7 @@ $ParseProvider, $RootScopeProvider, $QProvider, + $$QProvider, $$SanitizeUriProvider, $SceProvider, $SceDelegateProvider, @@ -222,6 +223,7 @@ function publishExternalAPI(angular){ $parse: $ParseProvider, $rootScope: $RootScopeProvider, $q: $QProvider, + $$q: $$QProvider, $sce: $SceProvider, $sceDelegate: $SceDelegateProvider, $sniffer: $SnifferProvider, diff --git a/src/ng/interval.js b/src/ng/interval.js index 909855bff5ba..3a9a9f3c9f3d 100644 --- a/src/ng/interval.js +++ b/src/ng/interval.js @@ -2,8 +2,8 @@ function $IntervalProvider() { - this.$get = ['$rootScope', '$window', '$q', - function($rootScope, $window, $q) { + this.$get = ['$rootScope', '$window', '$q', '$$q', + function($rootScope, $window, $q, $$q) { var intervals = {}; @@ -133,10 +133,10 @@ function $IntervalProvider() { function interval(fn, delay, count, invokeApply) { var setInterval = $window.setInterval, clearInterval = $window.clearInterval, - deferred = $q.defer(), - promise = deferred.promise, iteration = 0, - skipApply = (isDefined(invokeApply) && !invokeApply); + skipApply = (isDefined(invokeApply) && !invokeApply), + deferred = (skipApply ? $$q : $q).defer(), + promise = deferred.promise; count = isDefined(count) ? count : 0; diff --git a/src/ng/q.js b/src/ng/q.js index 4ac55423e6e8..f90a7c8f11b1 100644 --- a/src/ng/q.js +++ b/src/ng/q.js @@ -177,6 +177,13 @@ function $QProvider() { }]; } +function $$QProvider() { + this.$get = ['$browser', '$exceptionHandler', function($browser, $exceptionHandler) { + return qFactory(function(callback) { + $browser.defer(callback); + }, $exceptionHandler); + }]; +} /** * Constructs a promise manager. diff --git a/src/ng/timeout.js b/src/ng/timeout.js index 33a4dcde81ed..4b4c28256501 100644 --- a/src/ng/timeout.js +++ b/src/ng/timeout.js @@ -2,8 +2,8 @@ function $TimeoutProvider() { - this.$get = ['$rootScope', '$browser', '$q', '$exceptionHandler', - function($rootScope, $browser, $q, $exceptionHandler) { + this.$get = ['$rootScope', '$browser', '$q', '$$q', '$exceptionHandler', + function($rootScope, $browser, $q, $$q, $exceptionHandler) { var deferreds = {}; @@ -33,9 +33,9 @@ function $TimeoutProvider() { * */ function timeout(fn, delay, invokeApply) { - var deferred = $q.defer(), + var skipApply = (isDefined(invokeApply) && !invokeApply), + deferred = (skipApply ? $$q : $q).defer(), promise = deferred.promise, - skipApply = (isDefined(invokeApply) && !invokeApply), timeoutId; timeoutId = $browser.defer(function() { diff --git a/test/ng/intervalSpec.js b/test/ng/intervalSpec.js index 0d101246ebf7..48b7c59f1f02 100644 --- a/test/ng/intervalSpec.js +++ b/test/ng/intervalSpec.js @@ -98,6 +98,23 @@ describe('$interval', function() { })); + it('should NOT call $evalAsync or $digest if invokeApply is set to false', + inject(function($interval, $rootScope, $window, $timeout) { + var evalAsyncSpy = spyOn($rootScope, '$evalAsync').andCallThrough(); + var digestSpy = spyOn($rootScope, '$digest').andCallThrough(); + var notifySpy = jasmine.createSpy('notify'); + + $interval(notifySpy, 1000, 1, false); + + $window.flush(2000); + $timeout.flush(); // flush $browser.defer() timeout + + expect(notifySpy).toHaveBeenCalledOnce(); + expect(evalAsyncSpy).not.toHaveBeenCalled(); + expect(digestSpy).not.toHaveBeenCalled(); + })); + + it('should allow you to specify the delay time', inject(function($interval, $window) { var counter = 0; $interval(function() { counter++; }, 123); diff --git a/test/ng/timeoutSpec.js b/test/ng/timeoutSpec.js index 97c8448eedce..ec5bd8d25b8b 100644 --- a/test/ng/timeoutSpec.js +++ b/test/ng/timeoutSpec.js @@ -48,6 +48,22 @@ describe('$timeout', function() { })); + it('should NOT call $evalAsync or $digest if invokeApply is set to false', + inject(function($timeout, $rootScope) { + var evalAsyncSpy = spyOn($rootScope, '$evalAsync').andCallThrough(); + var digestSpy = spyOn($rootScope, '$digest').andCallThrough(); + var fulfilledSpy = jasmine.createSpy('fulfilled'); + + $timeout(fulfilledSpy, 1000, false); + + $timeout.flush(); + + expect(fulfilledSpy).toHaveBeenCalledOnce(); + expect(evalAsyncSpy).not.toHaveBeenCalled(); + expect(digestSpy).not.toHaveBeenCalled(); + })); + + it('should allow you to specify the delay time', inject(function($timeout, $browser) { var defer = spyOn($browser, 'defer'); $timeout(noop, 123);