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({