4

Let's say that I want to pass a Scheduler to an RxJS operator that makes it emit notifications every 5 seconds. Of course, this is very easy to do by just using interval or other existing operators. But if I really want to use a scheduler to accomplish that, how would I go about it?

My first thought is to subclass Rx.Scheduler.default. Would that be the way to go? And if so, how could that subclass look? Again, I understand that this is a complicated way to accomplish something that's easy using operators, but I am just curious about custom schedulers.

Sergi Mansilla
  • 12,495
  • 10
  • 39
  • 48
  • Learn more about what a Scheduler really is: http://stackoverflow.com/questions/28145890/what-is-a-scheduler-in-rxjs/28171587#28171587 – Brandon Oct 02 '15 at 13:51

2 Answers2

4

Operations should always be independent of the Schedulers that are used to implement them. Schedulers only know about one thing, time. Every scheduler is specifically built to deal with its own notion of time. They are expressly not built to handle specific operators since that would be a conflation of concerns.

So for your stated goal of creating a recurring task, I wouldn't recommend trying to actually create your own scheduler, it simply isn't needed. Schedulers come with an interface that already supports this.

You can use either the schedulePeriodic or the scheduleRecursiveFuture to accomplish this.

//Using periodic
Rx.Observable.interval = function(period, scheduler) {

  return Rx.Observable.create(function(observer) {
    return scheduler.schedulePeriodic(0, period, function(count) {
      observer.onNext(count);
      return count + 1;
    });
  });

};

//Using scheduleRecursive
Rx.Observable.interval = function(period, scheduler) {

  return Rx.Observable.create(function(observer) {
    return scheduler.scheduleRecursiveFuture(0, period, function(count, self) {
      observer.onNext(count);
      self(period, count + 1); 
    });
  });
};

Reference 1, Reference 2;

The former should be easier to wrap your head around, essentially it is just scheduling something to occur repeatedly spaced in time based on the period parameter.

The latter is usually a little more difficult to explain, but essentially you are scheduling a task and then sometime during the execution of that task you are rescheduling it (which is what the self parameter) is doing. This allows you do get the same effect using the period parameter.

The timing of this work is all directly affected by which scheduler you decide to pass into the operator. For instance, if you pass in the default it will try to use the best method for an asynchronous completion, whether that be setTimeout, setInterval or some other thing I can't remember. If you pass in a TestScheduler or a HistoricalScheduler this actually won't do anything until you increment each of their respective clocks, but doing so gives fine grained control over how time flows.

tl;dr Only implement new Schedulers if you have some new overall notion of time to express, otherwise use the existing API to do work on whatever Scheduler best fits how you want time to pass.

paulpdaniels
  • 18,395
  • 2
  • 51
  • 55
  • As always excellent description -- thank you so much for sharing your knowledge. When I start reading an answer for `rxjs` and did not see author yet, then with some very, very high probability can guess who the author is :) – artur grzesiak Oct 03 '15 at 07:10
0

Should you roll your own?

Plainly: No. Most likely you can get done what you need done with an existing operator. Something like buffer, window, sample, etc. Scheduler development is not completely straightforward.


How to roll your own RxJS 4 Scheduler

If you want to implement your own Scheduler, in RxJS 4, you'd subclass Rx.Scheduler, then override each schedule method: schedule, scheduleFuture, schedulePeriodic, scheduleRecursive, scheduleRecursiveFuture... You'd also likely want to override now to return something relevant to your schedule.

Here is an example of a custom scheduler that uses button clicks inside of real time

/**
NOTE: This is REALLY fast example. There is a lot that goes into implementing a 
Scheduler in RxJS, for example what would `now()` do in the scheduler below? It's also missing a number of scheduling methods.
*/

class ButtonScheduler extends Rx.Scheduler {
  /** 
    @param {string} the selector for the button (ex "#myButton")
  */
  constructor(selector) {
    super();
    this.button = document.querySelector(selector);
  }

  schedule(state, action) {
    const handler = (e) => {
      action(state);
    };
    const button = this.button;

    // next click the action will fire
    button.addEventListener('click', handler);

    return {
      dispose() {
        // ... unless you dispose of it
        button.removeEventListener('click', handler);
      }
    };
  }

  // Observable.interval uses schedulePeriodic
  schedulePeriodic(state, interval, action) {
    const button = this.button;

    let i = 0;
    const handler = (e) => {
      const count = i++;
      if(count > 0 && count % interval === 0) {
        state = action(state);
      }
    };

    // next click the action will fire
    button.addEventListener('click', handler);

    return {
      dispose() {
        // ... unless you dispose of it
        button.removeEventListener('click', handler);
      }
    };
  }
}

Rx.Observable.interval(1, new ButtonScheduler('#go'))
  .subscribe(x => {
    const output = document.querySelector('#output');
    output.innerText += x + '\n';
  });

How to do it in RxJS 5 (alpha)

Scheduling changed again in RxJS 5, since that version was rewritten from the ground up.

In RxJS5, you can create any object that adheres to the following interface:

interface Scheduler {
   now(): number
   schedule(action: function, delay: number = 0, state?: any): Subscription
}

Where Subscription is just any object with an unsubscribe function (same as dispose, really)

Once again, though, I don't advise creating a scheduler unless it's completely necessary.

I really hope that helps answer your question.

Ben Lesh
  • 107,825
  • 47
  • 247
  • 232