Skip to content

Commit

Permalink
feat: add scheduler for Run Loop
Browse files Browse the repository at this point in the history
  • Loading branch information
alexlafroscia committed Feb 7, 2019
1 parent 4dc34b3 commit 9a040fa
Show file tree
Hide file tree
Showing 7 changed files with 119 additions and 0 deletions.
1 change: 1 addition & 0 deletions addon/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { default as Route } from "./route";
export { default as scheduler } from "./scheduler";

export { default as subscribe } from "./decorators/subscribe";

Expand Down
23 changes: 23 additions & 0 deletions addon/scheduler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Scheduler, Subscription } from "rxjs";
import { cancel, later, run } from "@ember/runloop";

class RunLoopAction extends Subscription {
constructor(scheduler, work) {
super(() => cancel(this.timer));

this.scheduler = scheduler;
this.work = work;
}

schedule(state, delay) {
if (delay) {
this.timer = later(this, this.work, state, delay);
} else {
run(this, this.work, state);
}

return this;
}
}

export default new Scheduler(RunLoopAction);
22 changes: 22 additions & 0 deletions tests/dummy/app/pods/docs/features/scheduler/template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Run Loop Scheduler

RxJS has the concept of a [Scheduler][rxjs-scheduler], which is can use to queue work that should happen sometime in the future. Many Observables or Operators that deal with "time" can take accept a Scheduler as an argument if you need to customize how this work is planned.

Ember also has a means for scheduling tasks in the future; the [Run Loop][run-loop]. In order to schedule events from RxJS into the Run Loop, you can leverage the scheduler provided by `ember-rx`.

```javascript
import { scheduler as runLoopScheduler } from "ember-rx";
import { interval } from "rxjs";

const observable = interval(1000, runLoopScheduler);
const subscription = observable.subscribe(val => {
// Each emission from `interval` will be scheduled into a Run Loop
console.log(value);
});

// Unsubscribing will cancel any scheduled-but-not-run Run Loops
subscription.unsubscribe();
```

[rxjs-scheduler]: https://rxjs.dev/guide/scheduler
[run-loop]: https://guides.emberjs.com/release/applications/run-loop/
3 changes: 3 additions & 0 deletions tests/dummy/app/pods/docs/template.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
{{#viewer.nav as |nav|}}
{{nav.item 'Introduction' 'docs.index'}}

{{nav.section 'Features'}}
{{nav.item 'Run Loop Scheduler' 'docs.features.scheduler'}}

{{nav.section 'Cookbook'}}
{{nav.item 'What is this?' 'docs.cookbook.index'}}
{{nav.item 'Count Property Changes' 'docs.cookbook.count-prop-changes'}}
Expand Down
4 changes: 4 additions & 0 deletions tests/dummy/app/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ Router.map(function() {
this.route("cookbook", function() {
this.route("count-prop-changes");
});

this.route("features", function() {
this.route("scheduler");
});
});

this.route("testing", function() {
Expand Down
Empty file removed tests/unit/.gitkeep
Empty file.
66 changes: 66 additions & 0 deletions tests/unit/scheduler-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { module, test } from "qunit";
import td from "testdouble";
import { getSettledState, settled } from "@ember/test-helpers";
import { defer } from "rsvp";
import { from } from "rxjs";
import { delay } from "rxjs/operators";
import { scheduler as runloopScheduler } from "ember-rx";

module("Unit | Scheduler", function() {
test("it schedules immediate work using the Runloop", async function(assert) {
const deferred = defer();
const observable = from(deferred.promise, runloopScheduler);
const observer = td.function("observer");
observable.subscribe(observer);

deferred.resolve("resolution");

const state = getSettledState();

assert.ok(state.hasRunLoop, 'A run loop was started with the "work"');

assert.verify(
observer("resolution"),
{ times: 0 },
"Observer not yet called"
);

await settled();

assert.verify(
observer("resolution"),
{ times: 1 },
"Observer called once the runloop has settled"
);
});

module("delayed work", function() {
test("it schedules later work using the Runloop", async function(assert) {
const observable = from([1]).pipe(delay(10, runloopScheduler));
const observer = td.function("observer");
observable.subscribe(observer);

const state = getSettledState();

assert.ok(
state.hasPendingTimers,
"Work scheduled for later in the runloop"
);

await settled();

assert.verify(observer(1), "Eventually called with the scheduled work");
});

test("it can cancel a scheduled runloop", async function(assert) {
const observable = from([1]).pipe(delay(10, runloopScheduler));
const subscription = observable.subscribe(td.function("observer"));

subscription.unsubscribe();

const state = getSettledState();

assert.notOk(state.hasPendingTimers, "Scheduled work has been cancelled");
});
});
});

0 comments on commit 9a040fa

Please sign in to comment.