1

Will an interval which got piped run in RxJS?

Here is what I mean. Let us suppose we have the following code:

const arr = ["foo", "bar"];
const i = interval(500);
const toRun = i.pipe(
    map(index => arr[index]),
    take(arr.length)
);
toRun.subscribe(val => val);

Do I understand correctly, that the code works as follows:

1 The i is created, but won`t be run until we subscribe to it.

2 With the help of the pipe method we create a new Observable, which is built upon the i and works as follows:

  • each 500ms emit an iteration number (0, 1, 2, ...)
  • use the iteration number to extract a value from the arr
  • emit the extracted value to whoever was subscribed to the pipe method result
  • stop the emitting of the iteration numbers when the iteration number is greater than the arr.length

So, the toRun.subscribe(val => val); will emit foo, then after 500ms bar and will stop running. While the i will never emit anything, since no one has subscribed to it.

I want to understand how this works, so, please, correct my explanation to answer my question if I am wrong.

I stumbled other the question, while working through the Angular documentation. More specifically through the async pipe. I met there the following example:

import { Component } from '@angular/core';
import { Observable, interval } from 'rxjs';
import { map, take } from 'rxjs/operators';

@Component({
  selector: 'app-hero-message',
  template: `
    <h2>Async Hero Message and AsyncPipe</h2>
    <p>Message: {{ message$ | async }}</p>
    <button (click)="resend()">Resend</button>`,
})
export class HeroAsyncMessageComponent {
  message$: Observable<string>;

  private messages = [
    'You are my hero!',
    'You are the best hero!',
    'Will you be my hero?'
  ];

  constructor() { this.resend(); }

  resend() {
    this.message$ = interval(500).pipe(
      map(i => this.messages[i]),
      take(this.messages.length)
    );
  }
}

And I got curious whether or not there may be performance issues due to the unnecessary running of the interval(500) (since the pipe will create a new observable and the interval(500) won`t be used explicitly, but only by the pipe during the new observable creation).

Sasuke Uchiha
  • 857
  • 2
  • 11
  • 23
  • Your understanding of hot vs cold observables is a little off, as well as what pipe does with observables when it creates “new” streams. I’d recommend reading up on that. There’s no performance difference in the angular version vs your version. – bryan60 Jun 17 '20 at 13:52

2 Answers2

1

You pretty much understand it correctly. A few notes:

  • Yes, interval creates a cold observable that will only produce numbers when it's subscribed to.
  • pipe on its own won't create a new observable, the operators in pipe will.
  • map and take don't make an observable hot so it's still cold and will only "run" when it's subscribed to.
  • interval initially waits 500ms before emitting foo (after it's subscribed to)
  • take will unsubscribe from its source observable after the given amount of emissions. This will cause every Observable in the chain to unsubscribe from its source until eventually the interval will be unsubscribed from. So it's no longer emitting numbers.
frido
  • 13,065
  • 5
  • 42
  • 56
1

You think correctly and your code should work as intended but it doesn't look like the way RxJs should be used. Reaching out to a closure variable from within an operator seems a red flag against side-effect-free purity encouraged by RxJs.

There are many ways to do something in RxJs; if you start to think the RxJS way then you will figure out one of the right solutions. In your case, these two tips will take you to the right direction:

  • Wherever there's an array to take values from, think of how to extract observables from it using RxJs tools like from, of
  • Think of how to combine more than one observables and have them coordinate to produce the right result.

Replace your toRun assignment with this code:

 import {from, interval, zip} from "rxjs";
 // ...
 const toRun = zip(from(arr),i);  // where i is interval(500)

This solution is a simple but perfect example of how you use those two tips. It is much neater, and probably has better performance.

Under the hood, from turns the array into an observable, while zip uses the frequency of the interval observable to "moderate" the output of the array observable. Observables interacting like this is a typical pattern that you'll see very often in the way RxJs works.

Notes: As a bonus of using zip, in the subscribe handler. you'll get not just the interval value but also the corresponding array value, which are passed in an array parameter.

Son Nguyen
  • 1,412
  • 4
  • 10