-
Notifications
You must be signed in to change notification settings - Fork 3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(schedulers): fix asap and animationFrame schedulers to execute ac…
…ross async boundaries. (#1820) The AsapScheduler and AnimationFrameSchedulers were totally busted. My bad. Now they execute their scheduled actions in batches. If actions reschedule while executing a batch, a new frame is requested for the rescheduled action to execute in. This PR also simplifies the public `Scheduler` and `Action` APIs. Implementation details like the `actions` queue and `active` boolean are now on the concrete implementations, so it's easier for people to implement the Scheduler API. This PR also renames `FutureAction` -> `AsyncAction` to conform to the same naming convention as the rest of the Action types. Fixes #1814
- Loading branch information
Showing
32 changed files
with
735 additions
and
602 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
import {expect} from 'chai'; | ||
import * as Rx from '../../dist/cjs/Rx'; | ||
|
||
const animationFrame = Rx.Scheduler.animationFrame; | ||
|
||
/** @test {Scheduler} */ | ||
describe('Scheduler.animationFrame', () => { | ||
it('should exist', () => { | ||
expect(animationFrame).exist; | ||
}); | ||
|
||
it('should schedule an action to happen later', (done: MochaDone) => { | ||
let actionHappened = false; | ||
animationFrame.schedule(() => { | ||
actionHappened = true; | ||
done(); | ||
}); | ||
if (actionHappened) { | ||
done(new Error('Scheduled action happened synchronously')); | ||
} | ||
}); | ||
|
||
it('should execute recursively scheduled actions in separate asynchronous contexts', (done: MochaDone) => { | ||
let syncExec1 = true; | ||
let syncExec2 = true; | ||
animationFrame.schedule(function (index) { | ||
if (index === 0) { | ||
this.schedule(1); | ||
animationFrame.schedule(() => { syncExec1 = false; }); | ||
} else if (index === 1) { | ||
this.schedule(2); | ||
animationFrame.schedule(() => { syncExec2 = false; }); | ||
} else if (index === 2) { | ||
this.schedule(3); | ||
} else if (index === 3) { | ||
if (!syncExec1 && !syncExec2) { | ||
done(); | ||
} else { | ||
done(new Error('Execution happened synchronously.')); | ||
} | ||
} | ||
}, 0, 0); | ||
}); | ||
|
||
it('should cancel the animation frame if all scheduled actions unsubscribe before it executes', (done: MochaDone) => { | ||
let animationFrameExec1 = false; | ||
let animationFrameExec2 = false; | ||
const action1 = animationFrame.schedule(() => { animationFrameExec1 = true; }); | ||
const action2 = animationFrame.schedule(() => { animationFrameExec2 = true; }); | ||
expect(animationFrame.scheduled).to.exist; | ||
expect(animationFrame.actions.length).to.equal(2); | ||
action1.unsubscribe(); | ||
action2.unsubscribe(); | ||
expect(animationFrame.actions.length).to.equal(0); | ||
expect(animationFrame.scheduled).to.equal(undefined); | ||
animationFrame.schedule(() => { | ||
expect(animationFrameExec1).to.equal(false); | ||
expect(animationFrameExec2).to.equal(false); | ||
done(); | ||
}); | ||
}); | ||
|
||
it('should execute the rest of the scheduled actions if the first action is canceled', (done: MochaDone) => { | ||
let actionHappened = false; | ||
let firstSubscription = null; | ||
let secondSubscription = null; | ||
|
||
firstSubscription = animationFrame.schedule(() => { | ||
actionHappened = true; | ||
if (secondSubscription) { | ||
secondSubscription.unsubscribe(); | ||
} | ||
done(new Error('The first action should not have executed.')); | ||
}); | ||
|
||
secondSubscription = animationFrame.schedule(() => { | ||
if (!actionHappened) { | ||
done(); | ||
} | ||
}); | ||
|
||
if (actionHappened) { | ||
done(new Error('Scheduled action happened synchronously')); | ||
} else { | ||
firstSubscription.unsubscribe(); | ||
} | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import {expect} from 'chai'; | ||
import * as Rx from '../../dist/cjs/Rx'; | ||
|
||
const Scheduler = Rx.Scheduler; | ||
const queue = Scheduler.queue; | ||
|
||
/** @test {Scheduler} */ | ||
describe('Scheduler.queue', () => { | ||
it('should switch from synchronous to asynchronous at will', (done: MochaDone) => { | ||
let lastExecTime = 0; | ||
let asyncExec = false; | ||
queue.schedule(function (index) { | ||
if (index === 0) { | ||
lastExecTime = queue.now(); | ||
this.schedule(1, 100); | ||
} else if (index === 1) { | ||
if (queue.now() - lastExecTime < 100) { | ||
done(new Error('Execution happened synchronously.')); | ||
} else { | ||
asyncExec = true; | ||
lastExecTime = queue.now(); | ||
this.schedule(2, 0); | ||
} | ||
} else if (index === 2) { | ||
if (asyncExec === false) { | ||
done(new Error('Execution happened synchronously.')); | ||
} else { | ||
done(); | ||
} | ||
} | ||
}, 0, 0); | ||
asyncExec = false; | ||
}); | ||
it('should unsubscribe the rest of the scheduled actions if an action throws an error', () => { | ||
const actions = []; | ||
let action2Exec = false; | ||
let action3Exec = false; | ||
let errorValue = undefined; | ||
try { | ||
queue.schedule(() => { | ||
actions.push( | ||
queue.schedule(() => { throw new Error('oops'); }), | ||
queue.schedule(() => { action2Exec = true; }), | ||
queue.schedule(() => { action3Exec = true; }) | ||
); | ||
}); | ||
} catch (e) { | ||
errorValue = e; | ||
} | ||
expect(actions.every((action) => action.isUnsubscribed)).to.be.true; | ||
expect(action2Exec).to.be.false; | ||
expect(action3Exec).to.be.false; | ||
expect(errorValue).exist; | ||
expect(errorValue.message).to.equal('oops'); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.