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

Commit 920a380

Browse files
andyroogervojtajina
authored andcommitted
fix($timeout): clean deferreds immediately after callback exec/cancel
Make sure $timeout callbacks are forgotten about immediately after execution or cancellation. Previously when passing invokeApply=false, the cleanup used $q and so would be pending until the next $digest was triggered. This does not make a large functional difference, but can be very visible when looking at memory consumption of an app or debugging around the $$asyncQueue - these callbacks can have a big retaining tree.
1 parent f757f86 commit 920a380

File tree

2 files changed

+55
-5
lines changed

2 files changed

+55
-5
lines changed

src/ng/timeout.js

+4-5
Original file line numberDiff line numberDiff line change
@@ -45,17 +45,15 @@ function $TimeoutProvider() {
4545
deferred.reject(e);
4646
$exceptionHandler(e);
4747
}
48+
finally {
49+
delete deferreds[promise.$$timeoutId];
50+
}
4851

4952
if (!skipApply) $rootScope.$apply();
5053
}, delay);
5154

52-
cleanup = function() {
53-
delete deferreds[promise.$$timeoutId];
54-
};
55-
5655
promise.$$timeoutId = timeoutId;
5756
deferreds[timeoutId] = deferred;
58-
promise.then(cleanup, cleanup);
5957

6058
return promise;
6159
}
@@ -77,6 +75,7 @@ function $TimeoutProvider() {
7775
timeout.cancel = function(promise) {
7876
if (promise && promise.$$timeoutId in deferreds) {
7977
deferreds[promise.$$timeoutId].reject('canceled');
78+
delete deferreds[promise.$$timeoutId];
8079
return $browser.defer.cancel(promise.$$timeoutId);
8180
}
8281
return false;

test/ng/timeoutSpec.js

+51
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,27 @@ describe('$timeout', function() {
6868
}));
6969

7070

71+
it('should forget references to deferreds when callback called even if skipApply is true',
72+
inject(function($timeout, $browser) {
73+
// $browser.defer.cancel is only called on cancel if the deferred object is still referenced
74+
var cancelSpy = spyOn($browser.defer, 'cancel').andCallThrough();
75+
76+
var promise1 = $timeout(function() {}, 0, false);
77+
var promise2 = $timeout(function() {}, 100, false);
78+
expect(cancelSpy).not.toHaveBeenCalled();
79+
80+
$timeout.flushNext(0);
81+
82+
// Promise1 deferred object should already be removed from the list and not cancellable
83+
$timeout.cancel(promise1);
84+
expect(cancelSpy).not.toHaveBeenCalled();
85+
86+
// Promise2 deferred object should not have been called and should be cancellable
87+
$timeout.cancel(promise2);
88+
expect(cancelSpy).toHaveBeenCalled();
89+
}));
90+
91+
7192
describe('exception handling', function() {
7293

7394
beforeEach(module(function($exceptionHandlerProvider) {
@@ -106,6 +127,20 @@ describe('$timeout', function() {
106127

107128
expect(log).toEqual('error: Some Error');
108129
}));
130+
131+
132+
it('should forget references to relevant deferred even when exception is thrown',
133+
inject(function($timeout, $browser) {
134+
// $browser.defer.cancel is only called on cancel if the deferred object is still referenced
135+
var cancelSpy = spyOn($browser.defer, 'cancel').andCallThrough();
136+
137+
var promise = $timeout(function() { throw "Test Error"; }, 0, false);
138+
$timeout.flush();
139+
140+
expect(cancelSpy).not.toHaveBeenCalled();
141+
$timeout.cancel(promise);
142+
expect(cancelSpy).not.toHaveBeenCalled();
143+
}));
109144
});
110145

111146

@@ -147,5 +182,21 @@ describe('$timeout', function() {
147182
it('should not throw a runtime exception when given an undefined promise', inject(function($timeout) {
148183
expect($timeout.cancel()).toBe(false);
149184
}));
185+
186+
187+
it('should forget references to relevant deferred', inject(function($timeout, $browser) {
188+
// $browser.defer.cancel is only called on cancel if the deferred object is still referenced
189+
var cancelSpy = spyOn($browser.defer, 'cancel').andCallThrough();
190+
191+
var promise = $timeout(function() {}, 0, false);
192+
193+
expect(cancelSpy).not.toHaveBeenCalled();
194+
$timeout.cancel(promise);
195+
expect(cancelSpy).toHaveBeenCalledOnce();
196+
197+
// Promise deferred object should already be removed from the list and not cancellable again
198+
$timeout.cancel(promise);
199+
expect(cancelSpy).toHaveBeenCalledOnce();
200+
}));
150201
});
151202
});

0 commit comments

Comments
 (0)