From 7d722d4d36debcf5bb79fa4006f455bd72caeee1 Mon Sep 17 00:00:00 2001 From: Nicholas Jamieson Date: Tue, 28 Nov 2017 03:27:58 +1000 Subject: [PATCH] fix(scheduler): prevent unwanted clearInterval (#3044) * test(scheduler): add interval recycling tests * fix(scheduler): prevent unwanted clearInterval In AsyncAction, this.pending was assigned before the call to recycleAsyncId. That prevented the interval from being re-used resulting in the interval being cleared and re-created for each notification. Closes #3042 --- spec/schedulers/AsapScheduler-spec.ts | 53 +++++++++++++++++++++++++++ src/scheduler/AsyncAction.ts | 8 ++-- 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/spec/schedulers/AsapScheduler-spec.ts b/spec/schedulers/AsapScheduler-spec.ts index 1428dfb2be..7651f323e2 100644 --- a/spec/schedulers/AsapScheduler-spec.ts +++ b/spec/schedulers/AsapScheduler-spec.ts @@ -40,6 +40,59 @@ describe('Scheduler.asap', () => { sandbox.restore(); }); + it('should reuse the interval for recursively scheduled actions with the same delay', () => { + const sandbox = sinon.sandbox.create(); + const fakeTimer = sandbox.useFakeTimers(); + // callThrough is missing from the declarations installed by the typings tool in stable + const stubSetInterval = ( sinon.stub(global, 'setInterval')).callThrough(); + function dispatch(state: any): void { + state.index += 1; + if (state.index < 3) { + ( this).schedule(state, state.period); + } + } + const period = 50; + const state = { index: 0, period }; + asap.schedule(dispatch, period, state); + expect(state).to.have.property('index', 0); + expect(stubSetInterval).to.have.property('callCount', 1); + fakeTimer.tick(period); + expect(state).to.have.property('index', 1); + expect(stubSetInterval).to.have.property('callCount', 1); + fakeTimer.tick(period); + expect(state).to.have.property('index', 2); + expect(stubSetInterval).to.have.property('callCount', 1); + stubSetInterval.restore(); + sandbox.restore(); + }); + + it('should not reuse the interval for recursively scheduled actions with a different delay', () => { + const sandbox = sinon.sandbox.create(); + const fakeTimer = sandbox.useFakeTimers(); + // callThrough is missing from the declarations installed by the typings tool in stable + const stubSetInterval = ( sinon.stub(global, 'setInterval')).callThrough(); + function dispatch(state: any): void { + state.index += 1; + state.period -= 1; + if (state.index < 3) { + ( this).schedule(state, state.period); + } + } + const period = 50; + const state = { index: 0, period }; + asap.schedule(dispatch, period, state); + expect(state).to.have.property('index', 0); + expect(stubSetInterval).to.have.property('callCount', 1); + fakeTimer.tick(period); + expect(state).to.have.property('index', 1); + expect(stubSetInterval).to.have.property('callCount', 2); + fakeTimer.tick(period); + expect(state).to.have.property('index', 2); + expect(stubSetInterval).to.have.property('callCount', 3); + stubSetInterval.restore(); + sandbox.restore(); + }); + it('should schedule an action to happen later', (done: MochaDone) => { let actionHappened = false; asap.schedule(() => { diff --git a/src/scheduler/AsyncAction.ts b/src/scheduler/AsyncAction.ts index 31a95c73ef..432edbab2a 100644 --- a/src/scheduler/AsyncAction.ts +++ b/src/scheduler/AsyncAction.ts @@ -29,10 +29,6 @@ export class AsyncAction extends Action { // Always replace the current state with the new state. this.state = state; - // Set the pending flag indicating that this action has been scheduled, or - // has recursively rescheduled itself. - this.pending = true; - const id = this.id; const scheduler = this.scheduler; @@ -61,6 +57,10 @@ export class AsyncAction extends Action { this.id = this.recycleAsyncId(scheduler, id, delay); } + // Set the pending flag indicating that this action has been scheduled, or + // has recursively rescheduled itself. + this.pending = true; + this.delay = delay; // If this action has already an async Id, don't request a new one. this.id = this.id || this.requestAsyncId(scheduler, this.id, delay);