Basic Operators

We've made observables from values, arrays, and promises. What about generators? They're iterables in JavaScript, so they should be fair game—and they are.

But, we run into a little bit of a problem when these generators don't ever end.

export function* fibonacci() {
  let values = [0, 1];

  while (true) {
    let [current, next] = values;

    yield current;

    values = [next, current + next];
  }
}

Introducing Operators

Using from here would totally work, but we'd end up locking up the main thread as our observable just worked through the values forever and ever. We could add a condition to the while loop to break it off after a certain number of iterations, but we don't need to. Why? Because we have RxJS!

take

Take a certain number of values from an observable and then stop.

const example$ = from(fibonacci()).pipe(take(10));

example$.subscribe((val) => console.log(val));
// Logs: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34

skip

Ignore the first however many values and then start listening.

const example$ = from([1, 2, 3, 4, 5]).pipe(skip(2));

example$.subscribe((val) => console.log(val));
// Logs: 3, 4, 5

takeWhile and skipWhile

take and skip have siblings that will take a function instead of an integer.

const under200$ = from(fibonacci()).pipe(takeWhile((value) => value < 200));

under200$.subscribe(console.log);

// Logs: 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144
const over100$ = from(fibonacci()).pipe(
  skipWhile((value) => value < 100),
  take(4),
);

over100$.subscribe(console.log);

// Logs:  144, 233, 377, 610

filter

This one works just like it does with arrays.

const evenNumbers$ = of(1, 2, 3, 4, 5, 6, 7, 8).pipe(
  filter((n) => n % 2 === 0),
);

evenNumbers$.subscribe((val) => console.log(val));
// Logs: 2, 4, 6, 8

map

So does map.

const doubledNumbers$ = of(1, 2, 3).pipe(map((n) => n * 2));

doubledNumbers$.subscribe(console.log);
// Logs: 2, 4, 6

mapTo

mapTo is just a simplified version of map.

const over100$ = from(fibonacci()).pipe(
  skipWhile((value) => value < 100),
  take(4),
  mapTo('HELLO!'),
);

over100$.subscribe(console.log);

// Logs: "HELLO!", "HELLO!", "HELLO!", "HELLO!"

reduce

The thing to keep in mind with reduce is that it only emits one value: the final value upon completion. If you need each intermediate value, you'll want to use scan.

const under200$ = from(fibonacci()).pipe(
  takeWhile((value) => value < 200),
  reduce((total, value) => total + value, 0),
);

under200$.subscribe(console.log);

// Logs: 375

scan

scan behaves like reduce, but it also gives us every intermediate value along the way.

const under200$ = from(fibonacci()).pipe(
  takeWhile((value) => value < 200),
  reduce((total, value) => total + value, 0),
);

under200$.subscribe(console.log);

// Logs: 1, 3, 6, 11, 19, 32, 53, 87, 142, 231, 375

We can also combine it with take if we wanted to use a certain number of values.

const fibonacci$ = range(0, Infinity).pipe(
  scan(([curr, next]) => [next, curr + next], [0, 1]),
  map(([curr]) => curr),
  take(5),
);

fibonacci$.subscribe(console.log);

tap

One of the problems with .pipe is that you you only get the value at the very end. This can be tricky for debugging. tap allows you to do something and immediately return the value that you started with. This can be useful for side effects—most notably logging to the console and manipulating the DOM.

const div = document.querySelector('div');

const example$ = from([1, 2, 3, 4]).pipe(
  tap((value) => console.log(`About to set the <div> to ${value}.`)),
  tap((value) => {
    div.innerText = value;
  }),
  tap((value) => console.log(`Set the <div> to ${value}.`)),
);

Your Mission

In exercises/basic-operators.test.js, there are a series of quick exercises. Your job is to make the tests pass.