2

I have the following code:

    final Observable<String> a = Observable.just("a1", "a2");   
    final Observable<String> b = Observable.just("b1");

    final Observable<String> c = Observable.combineLatest(a, b, (first, second) -> first + second);

    c.subscribe(res -> System.out.println(res));

What is expected output? I would have expected

a1b1
a2b1

But the actual output is

a2b1

Does that make sense? What is the correct operator the generate the expected sequence?

Carsten
  • 468
  • 4
  • 16
  • That's an expected behavior. You can experiment with the operator by dragging the emissions in [rxmarbles](http://rxmarbles.com/#combineLatest). – azizbekian Feb 20 '18 at 16:45

2 Answers2

1

Good question! Seems like perhaps a race condition. combineLatest won't output anything until both sources have emitted, and it appears that by the time b generates its output, a has already moved on to its second item. In a "real world" application with asynchronous events that are spaced out in time, you would probably get the behavior you want.

If you can stand the wait, a solution would be to delay a's outputs a bit. With a bit more work you could delay just the first output (see the various overloads of the delay operator). Also I just noticed there's an operator delaySubscription that would probably do the trick (delay your subscription to a until b emits something). I'm sure there are other, perhaps better, solutions (I'm still learning myself).

Robert Lewis
  • 1,847
  • 2
  • 18
  • 43
  • Indeed this works if I make the emissions of a and b time based, by zipping with a timer. However it feels strange that the behaviour of combineLatest should rely on this and not follow the spec. In particular when using this in a library usecase I don't know where the source observables comes from and if they are async or not. – Carsten Feb 20 '18 at 16:10
  • I think it's arguable whether this violates the spec. The spec, after all, doesn't say anything about the exact timing of events, and there are a number of operators that make no guarantees about what order things appear in. – Robert Lewis Feb 20 '18 at 19:08
1

As the name of the operator should imply, it combines the latest value of each source. If the sources are synchronous or really fast, this could mean that one or more sources will run to their completion and the operator will remember only the very last values of each. You have to interleave the source values by some means, such as having asynchronous sources with ample amount of time between items and avoid close overlapping of items of multiple sources.

The expected sequence can be generated a couple of ways, depending on what your original intention was. For example, if you wanted all cross combination, use flatMap:

a.flatMap(aValue -> b, (aValue, bValue) -> first + second)
.subscribe(System.out::println);

If b is something expensive to recreate, cache it:

Observable<String> cachedB = b.cache();
a.flatMap(aValue -> cachedB, (aValue, bValue) -> first + second)
.subscribe(System.out::println);
akarnokd
  • 69,132
  • 14
  • 157
  • 192