From b618b5aa86138c900055c5496967e3348a7b98fc Mon Sep 17 00:00:00 2001 From: arturovt Date: Mon, 20 Jun 2022 12:13:58 +0300 Subject: [PATCH] fix(zone.js): cancel tasks only when they are scheduled or running (#46435) Currently, there's no check if the task (that is being canceled) has the right state. Only `scheduled` and `running` tasks can be canceled. If the task has a non-appropriate state, then an error will be thrown. Cancelation should not throw an error on an already canceled task, e.g. `clearTimeout` does not throw errors when it's called multiple times on the same timer. PR Close #45711 PR Close #46435 --- packages/zone.js/lib/zone.ts | 5 +++++ packages/zone.js/test/common/task.spec.ts | 22 ++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/packages/zone.js/lib/zone.ts b/packages/zone.js/lib/zone.ts index dc1f7b26c1e60..488c8f76c421e 100644 --- a/packages/zone.js/lib/zone.ts +++ b/packages/zone.js/lib/zone.ts @@ -956,6 +956,11 @@ const Zone: ZoneType = (function(global: any) { throw new Error( 'A task can only be cancelled in the zone of creation! (Creation: ' + (task.zone || NO_ZONE).name + '; Execution: ' + this.name + ')'); + + if (task.state !== scheduled && task.state !== running) { + return; + } + (task as ZoneTask)._transitionTo(canceling, scheduled, running); try { this._zoneDelegate.cancelTask(this, task); diff --git a/packages/zone.js/test/common/task.spec.ts b/packages/zone.js/test/common/task.spec.ts index ff8721567d400..9718a32abacd1 100644 --- a/packages/zone.js/test/common/task.spec.ts +++ b/packages/zone.js/test/common/task.spec.ts @@ -907,6 +907,28 @@ describe('task lifecycle', () => { })); }); + // Test specific to https://github.com/angular/angular/issues/45711 + it('should not throw an error when the task has been canceled previously and is attemped to be canceled again', + () => { + testFnWithLoggedTransitionTo(() => { + Zone.current.fork({name: 'testCancelZone'}).run(() => { + const task = + Zone.current.scheduleEventTask('testEventTask', noop, undefined, noop, noop); + Zone.current.cancelTask(task); + Zone.current.cancelTask(task); + }); + expect(log.map(item => { + return {toState: item.toState, fromState: item.fromState}; + })) + .toEqual([ + {toState: 'scheduling', fromState: 'notScheduled'}, + {toState: 'scheduled', fromState: 'scheduling'}, + {toState: 'canceling', fromState: 'scheduled'}, + {toState: 'notScheduled', fromState: 'canceling'} + ]); + }); + }); + describe('reschedule zone', () => { let callbackLogs: ({pos: string, method: string, zone: string, task: string}|HasTaskState)[]; const newZone = Zone.root.fork({