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

Commit 19b6b34

Browse files
committed
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. BREAKING CHANGE Previously, even if invokeApply was set to false, a $rootScope digest would occur during promise resolution. This is no longer the case, as promises returned from $timeout and $interval will no longer trigger $evalAsync (which in turn causes a $digest) if `invokeApply` is false. Workarounds include manually triggering $scope.$apply(), or returning $q.defer().promise from a promise callback, and resolving or rejecting it when appropriate. var interval = $interval(function() { if (someRequirementFulfilled) { $interval.cancel(interval); $scope.$apply(); } }, 100, 0, false); or: var interval = $interval(function (idx) { // make the magic happen }, 1000, 10, false); interval.then(function(idx) { var deferred = $q.defer(); // do the asynchronous magic --- $evalAsync will cause a digest and cause // bindings to update. return deferred.promise; }); Closes #7999 Closes #7103
1 parent b28b5ca commit 19b6b34

File tree

6 files changed

+51
-9
lines changed

6 files changed

+51
-9
lines changed

src/AngularPublic.js

+2
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
$ParseProvider,
7474
$RootScopeProvider,
7575
$QProvider,
76+
$$QProvider,
7677
$$SanitizeUriProvider,
7778
$SceProvider,
7879
$SceDelegateProvider,
@@ -222,6 +223,7 @@ function publishExternalAPI(angular){
222223
$parse: $ParseProvider,
223224
$rootScope: $RootScopeProvider,
224225
$q: $QProvider,
226+
$$q: $$QProvider,
225227
$sce: $SceProvider,
226228
$sceDelegate: $SceDelegateProvider,
227229
$sniffer: $SnifferProvider,

src/ng/interval.js

+5-5
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33

44
function $IntervalProvider() {
5-
this.$get = ['$rootScope', '$window', '$q',
6-
function($rootScope, $window, $q) {
5+
this.$get = ['$rootScope', '$window', '$q', '$$q',
6+
function($rootScope, $window, $q, $$q) {
77
var intervals = {};
88

99

@@ -133,10 +133,10 @@ function $IntervalProvider() {
133133
function interval(fn, delay, count, invokeApply) {
134134
var setInterval = $window.setInterval,
135135
clearInterval = $window.clearInterval,
136-
deferred = $q.defer(),
137-
promise = deferred.promise,
138136
iteration = 0,
139-
skipApply = (isDefined(invokeApply) && !invokeApply);
137+
skipApply = (isDefined(invokeApply) && !invokeApply),
138+
deferred = (skipApply ? $$q : $q).defer(),
139+
promise = deferred.promise;
140140

141141
count = isDefined(count) ? count : 0;
142142

src/ng/q.js

+7
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,13 @@ function $QProvider() {
173173
}];
174174
}
175175

176+
function $$QProvider() {
177+
this.$get = ['$browser', '$exceptionHandler', function($browser, $exceptionHandler) {
178+
return qFactory(function(callback) {
179+
$browser.defer(callback);
180+
}, $exceptionHandler);
181+
}];
182+
}
176183

177184
/**
178185
* Constructs a promise manager.

src/ng/timeout.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33

44
function $TimeoutProvider() {
5-
this.$get = ['$rootScope', '$browser', '$q', '$exceptionHandler',
6-
function($rootScope, $browser, $q, $exceptionHandler) {
5+
this.$get = ['$rootScope', '$browser', '$q', '$$q', '$exceptionHandler',
6+
function($rootScope, $browser, $q, $$q, $exceptionHandler) {
77
var deferreds = {};
88

99

@@ -33,9 +33,9 @@ function $TimeoutProvider() {
3333
*
3434
*/
3535
function timeout(fn, delay, invokeApply) {
36-
var deferred = $q.defer(),
36+
var skipApply = (isDefined(invokeApply) && !invokeApply),
37+
deferred = (skipApply ? $$q : $q).defer(),
3738
promise = deferred.promise,
38-
skipApply = (isDefined(invokeApply) && !invokeApply),
3939
timeoutId;
4040

4141
timeoutId = $browser.defer(function() {

test/ng/intervalSpec.js

+17
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,23 @@ describe('$interval', function() {
9898
}));
9999

100100

101+
it('should NOT call $evalAsync or $digest if invokeApply is set to false',
102+
inject(function($interval, $rootScope, $window, $timeout) {
103+
var evalAsyncSpy = spyOn($rootScope, '$evalAsync').andCallThrough();
104+
var digestSpy = spyOn($rootScope, '$digest').andCallThrough();
105+
var notifySpy = jasmine.createSpy('notify');
106+
107+
$interval(notifySpy, 1000, 1, false);
108+
109+
$window.flush(2000);
110+
$timeout.flush(); // flush $browser.defer() timeout
111+
112+
expect(notifySpy).toHaveBeenCalledOnce();
113+
expect(evalAsyncSpy).not.toHaveBeenCalled();
114+
expect(digestSpy).not.toHaveBeenCalled();
115+
}));
116+
117+
101118
it('should allow you to specify the delay time', inject(function($interval, $window) {
102119
var counter = 0;
103120
$interval(function() { counter++; }, 123);

test/ng/timeoutSpec.js

+16
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,22 @@ describe('$timeout', function() {
4848
}));
4949

5050

51+
it('should NOT call $evalAsync or $digest if invokeApply is set to false',
52+
inject(function($timeout, $rootScope) {
53+
var evalAsyncSpy = spyOn($rootScope, '$evalAsync').andCallThrough();
54+
var digestSpy = spyOn($rootScope, '$digest').andCallThrough();
55+
var fulfilledSpy = jasmine.createSpy('fulfilled');
56+
57+
$timeout(fulfilledSpy, 1000, false);
58+
59+
$timeout.flush();
60+
61+
expect(fulfilledSpy).toHaveBeenCalledOnce();
62+
expect(evalAsyncSpy).not.toHaveBeenCalled();
63+
expect(digestSpy).not.toHaveBeenCalled();
64+
}));
65+
66+
5167
it('should allow you to specify the delay time', inject(function($timeout, $browser) {
5268
var defer = spyOn($browser, 'defer');
5369
$timeout(noop, 123);

0 commit comments

Comments
 (0)