简体   繁体   中英

RxJS - An observable 'A' doesn't stop emitting with a takeUntil that is piped from the same observable 'A'

I have an observable that prints a number to the console every second after a start button is clicked. The observable should stop printing 5 seconds after the start button is clicked.

Start Condition every second after the start button is clicked

End Condition five seconds after the start button is clicked

The code that doesn't work

const startClick$ = fromEvent(document.getElementById('start'), 'click');
const stop$ = startClick$.pipe(switchMapTo(interval(5000)));
const printInterval$ = interval(1000).pipe(takeUntil(stop$));
const startPrint$ = startClick$.pipe(switchMapTo(printInterval$));

startPrint$.subscribe(
  value => {
    console.log('new value is:', value);
  },
  () => {
    console.log('there was some error!');
  },
  () => {
    console.log('i have completed');
  }
);

stop$.subscribe(() => {
  console.log('i should stop right now!!');
});

After five seconds elapse after clicking the start button, the startPrint$ Observable does not stop emitting.

The code that works

const endClick$ = fromEvent(document.getElementById('end'), 'click');
const stopWithEndClick$ = endClick$.pipe(switchMapTo(interval(5000)));
const printInterval1$ = interval(1000).pipe(takeUntil(stopWithEndClick$));
const startPrint1$ = startClick$.pipe(switchMapTo(printInterval1$));

startPrint1$.subscribe(
  value => {
    console.log('1: new value is:', value);
  },
  () => {
    console.log('1: there was some error!');
  },
  () => {
    console.log('1: i have completed');
  }
);

stopWithEndClick$.subscribe(() => {
  console.log('1: i should stop right now!!');
});

Here i have another button end , and i takeUntil 5 seconds after the end button is clicked. In this case the observable stops emitting as expected.

How can i make the first case work, am I making any mistakes here? the entire working code can be found at https://stackblitz.com/edit/take-until-issue?file=index.ts

This is happening because of the order of subscriptions inside RxJS operators. When you first click the "start" button, the first who receives the notification is stop$ but none is listening to stop$ so the notification doesn't trigger anything. You're seeing "'i should stop right now!!' only because you subscribe yourself at the end.

So the easiest solution is creating the stop$ chain as an Observable that will start emitting only after you subscribe like timer(5000) that emits just once and then completes.

const stop$ = timer(5000);

Your updated demo: https://stackblitz.com/edit/take-until-issue-or4okm?file=index.ts

Just be aware, that you might get different number of results because RxJS (JavaScript in general) can't guarantee that 5000ms is going to be exactly 5000ms . The same applies to 1000ms so sometimes the events might be triggered in a different order. Better use for example take(5) if you want only 5 results. Also for logging it's better to use for example tap(v => console.log(v)) so you won't make a another subscription.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM