Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unexpected withLatestFrom operator behavior #5558

Closed
stas-karmanov opened this issue Jul 2, 2020 · 2 comments
Closed

Unexpected withLatestFrom operator behavior #5558

stas-karmanov opened this issue Jul 2, 2020 · 2 comments

Comments

@stas-karmanov
Copy link

Bug Report

Current Behavior

const bh1 = new BehaviorSubject<string>(null);
const bh2 = new BehaviorSubject<string>(null);

bh1.pipe(filter(Boolean), tap(console.log)).subscribe(bh2);
bh2.pipe(withLatestFrom(bh1)).subscribe(console.warn);

bh1.next('BH');

You'll see:
image

bh1 value was updated but withLatestFrom operator took previous (null). The problem can be solved using debounceTime:

bh2.pipe(debounceTime(0), withLatestFrom(bh1)).subscribe(console.warn);

But I don't want to use such "workarounds" for many reasons.

Expected behavior
withLatestFrom should pull the latest state even if it was updated synchronously.

Environment

  • Runtime: Chrome v83.0.4103.116
  • RxJS version: v6.5.5
@josepot
Copy link
Contributor

josepot commented Jul 2, 2020

Hi @stas-karmanov ,

This is not a bug. Have you noticed that another way of getting the behavior that you are looking for is to change the order of the subscriptions? Like this:

const bh1 = new BehaviorSubject<string>(null);
const bh2 = new BehaviorSubject<string>(null);

bh2.pipe(withLatestFrom(bh1)).subscribe(console.warn);
bh1
  .pipe(
    filter(Boolean),
    tap(console.log)
  )
  .subscribe(bh2);

bh1.next("BH");

If changing the order of the subscriptions is not an option, then you could also observe bh1 through the asapScheduler, like this:

const bh1 = new BehaviorSubject<string>(null);
const bh2 = new BehaviorSubject<string>(null);

bh1
  .pipe(
    observeOn(asapScheduler),
    filter(Boolean),
    tap(console.log)
  )
  .subscribe(bh2);
bh2.pipe(withLatestFrom(bh1)).subscribe(console.warn);

bh1.next("BH");

If you think about it carefully, it does make sense that your code is behaving in the way that's behaving. Notice that this line:

bh2.pipe(withLatestFrom(bh1)).subscribe(console.warn);

is roughly the equivalent of:

new Observable(observer => {
  let latestVal: string | null;
  const bh1Subscription = bh1.subscribe(x => {
    latestVal = x;
  });

  const bh2Subscription = bh2.subscribe({
    next(val) {
      observer.next([val, latestVal] as const);
    },
    complete() {
      observer.complete();
    },
    error(e: any) {
      observer.error(e);
    }
  });
  return () => {
    bh2Subscription.unsubscribe();
    bh1Subscription.unsubscribe();
  };
}).subscribe(console.warn);

Notice that there are 2 subscribers listening to bh1? One that you have created yourself and the one that withLatestFrom has created, which in the "raw" version above I have named bh1Subscription. Since "yours" got subscribed first, it will also get notified first, right?

That means that when it gets notified, it will synchronously perform a next to bh2, whicn in its turn will immediately notify the bh2Subscription. Therefore it's only after this subscription chain finishes that the bh1Subscription will be notified. That's why switching the order of the subscriptions produces the behavior that you are after. Because that makes the subscription created by the withLatestFrom operator to get subscribed (and notified) first.

@stas-karmanov
Copy link
Author

@josepot thank you a lot for explanation!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants