2

There are two observables that may be emiting together or separately: stream1 and stream2. I need my subscription to fire only if stream2 fires less then 1 second after stream1 does.
Any way to achieve that with RxJS?

Yan Sidorov
  • 430
  • 1
  • 3
  • 12

2 Answers2

1

You can use a timestamp and withLatestFrom to decide which values to emit Here I just filter such that only values that meet your condition pass through.

stream2.pipe(
  timestamp(),
  withLatestFrom(stream1.pipe(
    timestamp(),
    startWith({timestamp: 0, value: null})
  )),
  filter(([s2, s1]) => s2.timestamp - s1.timestamp < 1000),
  map(([s2, s1]) => ({
    stream1: s1.value,
    stream2: s2.value
  }))
);
Mrk Sef
  • 7,557
  • 1
  • 9
  • 21
0

The following solution really will only work with "hot" Observables because I'm subscribing to the second Observable only after the first one emits:

const stream1$ = timer(1000);
const stream2$ = timer(800);

stream1$
  .pipe(
    switchMap(val1 => stream2$.pipe(
      timeout(1000),
      catchError(() => EMPTY),
      map(val2 => [val1, val2]),
    ))
  )
  .subscribe(console.log);

timeout() will emit an error when stream2$ doesn't emit in less than 1s and the error is immediatelly caught by catchError and ignored.

To use this solution with cold Observables you can turn them into hot.

const stream1$ = timer(1000);
const stream2$ = timer(1800);
const stream2$Published = stream2$.pipe(publish()) as ConnectableObservable<any>;
const subscription = stream2$Published.connect();

stream1$
  .pipe(
    switchMap(val1 => stream2$Published.pipe(
      timeout(1000),
      catchError(() => EMPTY),
      map(val2 => [val1, val2]),
    ))
  )
  .subscribe(console.log);

Live demo: https://stackblitz.com/edit/rxjs-mqudah?devtoolsheight=60

I think this is a rare case where using publish() is actually useful. Otherwise, a little shorter solution might be like the following that will work with cold Observables as well even though it's not as easy to understand:

const stream1$ = timer(400);
const stream2$ = timer(1200);

combineLatest([
  stream1$.pipe(timestamp()),
  stream2$.pipe(timestamp()),
]).pipe(
  // Check that stream1$ emited first && stream2$ emited less than 1000ms after stream1$.
  filter(([val1, val2]) => (val1.timestamp < val2.timestamp && (val2.timestamp - val1.timestamp) < 1000)),
  map(([val1, val2]) => [val1.value, val2.value]),
).subscribe(console.log);

Live demo: https://stackblitz.com/edit/rxjs-yfhyyh?devtoolsheight=60

However, it's probably not very obvious what the filter() does from quick glance and has some "cognitive comlexity" :).

martin
  • 93,354
  • 25
  • 191
  • 226