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

fix($timeout/$interval): do not trigger a digest when cancelling a $timeout/$interval #16064

Merged
merged 1 commit into from
Jul 3, 2017

Conversation

jbedard
Copy link
Collaborator

@jbedard jbedard commented Jun 21, 2017

Previously .catch(noop) was used on the rejected timeout/interval to prevent an unhandled rejection error. However this would schedule a deferred task to run the noop. If the cancelling was outside a digest this could cause a new digest such as with the ng-model debounce option.

Fixes #16057

@@ -1328,8 +1328,7 @@ describe('ngAnimate $animateCss', function() {
animator.end();

expect(element.data(ANIMATE_TIMER_KEY)).toBeUndefined();
$timeout.flush();
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an example of how this change can be "breaking" for some tests.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So it's probably not a breaking change? Should we mention this in the review anyway? (But no under Breaking Changes)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jbedard Can you clarify how this can be "breaking"?

@jbedard
Copy link
Collaborator Author

jbedard commented Jun 21, 2017

For now I have not removed the 2 promise.catch(noop) calls from resource.js. I think both are already cases where another promise will be resolved so I didn't think it was worth either a) exposing the new helper or b) accessing the internal $$state.pur.

@Narretz
Copy link
Contributor

Narretz commented Jun 23, 2017

Just saw that the unit tests failed with lots of compile test failures.

src/ng/q.js Outdated
@@ -378,8 +378,8 @@ function qFactory(nextTick, exceptionHandler, errorOnUnhandledRejections) {
// eslint-disable-next-line no-unmodified-loop-condition
while (!queueSize && checkQueue.length) {
var toCheck = checkQueue.shift();
if (!toCheck.pur) {
toCheck.pur = true;
if (isStateExceptionHandled(toCheck)) {
Copy link
Contributor

@Narretz Narretz Jun 26, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this must be !isStateExceptionHandled(toCheck) and will fix all the failing tests except one

@@ -459,11 +459,12 @@ describe('ngModelOptions', function() {
$rootScope.$watch(watchSpy);

helper.changeInputValueTo('a');
expect(function() {$browser.defer.flush();}).toThrowError('No deferred tasks to be flushed');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't work correctly. It still flushes the task and triggers a digest. I haven't found out why exactly it doesn't work, but I suspect it's because of a mismatch between the real browser and the mock browser.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The assumption that there are no deferred tasks is wrong at this point, because there's obviously a pending timeout.
We just need to check that the watchFn doesn't get called.
If you put in this:

          helper.changeInputValueTo('a');
          $timeout.flush(2000);
          helper.changeInputValueTo('b');
          $timeout.flush(2000);

the test will fail without the patch. The second timeout.flush is crucial - without it, the watchFn spy doesn't get called, but with it, it gets called twice (without the patch).

@jbedard
Copy link
Collaborator Author

jbedard commented Jun 28, 2017

Updated. Thanks for pointing out the issues @Narretz

@@ -449,7 +449,7 @@ describe('ngModelOptions', function() {
});


it('should not trigger digest while debouncing', function() {
it('should not trigger digest while debouncing', inject(function($browser) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because it was calling $browser.defer.flush() like the other tests (which is wrong in this case). I'll undo this...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

Previously `.catch(noop)` was used on the rejected timeout/interval to prevent an unhandled rejection error. However this would schedule a deferred task to run the `noop`. If the cancelling was outside a digest this could cause a new digest such as with the ng-model `debounce` option.

Fixes angular#16057
@Narretz Narretz merged commit cdaa6a9 into angular:master Jul 3, 2017
Narretz pushed a commit that referenced this pull request Jul 3, 2017
Previously, `.catch(noop)` was used on a rejected timeout/interval to prevent an unhandled rejection error (introduced in #c9dffde1cb). However this would schedule a deferred task to run the `noop`. If the cancelling was outside a digest this could cause a new digest such as with the ng-model `debounce` option.

For unit testing, this means that it's no longer necessary to use `$timeout.flush()` when a `$timeout` has been cancelled outside of a digest. Previously, this was necessary to execute the deferred task added by `.catch(noop).
There's an example of such a change in this commit's changeset in the file `/test/ngAnimate/animateCssSpec.js`.

Fixes #16057
Closes #16064
@abobwhite
Copy link

Would this have affected $timeout.cancel from ever returning true in some cases? I have code that used to work fine but now the cancel function always returns false and the timeout is never cancelled.

@jbedard
Copy link
Collaborator Author

jbedard commented Jan 27, 2018

I don't think this would have changed the return value. Also doesn't look like $browser.defer.cancel changed anytime recently (but I didn't look too hard)...

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants