Why does my RxJava Observable emit only to the fir

2019-06-03 17:21发布

问题:

Can someone explain why the below test fails?

public class ObservableTest {
    @Test
    public void badObservableUsedTwiceDoesNotEmitToSecondConsumer() {
        // Any simpler observable makes the test pass
        Observable<Integer> badObservable = Observable.just(1)
                .zipWith(Observable.just(2), (one, two) -> Observable.just(3))
                .flatMap(observable -> observable);

        ObservableCalculator calc1 = new ObservableCalculator(badObservable);
        ObservableCalculator calc2 = new ObservableCalculator(badObservable);

        // zipping causes the failure
        // Calling calculate().toBlocking().subscribe() on each calc passes
        // Observable.from(listOfCalcs).flatMap(calc -> calc.calculate()) passes
        Observable.zip(ImmutableList.of(calc1.calculate(), calc2.calculate()), results -> results)
                .toBlocking()
                .subscribe();

        assertThat(calc1.hasCalculated).isTrue();
        assertThat(calc2.hasCalculated).isTrue(); // this fails
    }

    private static class ObservableCalculator {
        private final Observable<?> observable;

        public boolean hasCalculated = false;

        public ObservableCalculator(Observable<?> observable) {
            this.observable = observable;
        }

        public Observable<Void> calculate() {
            return observable.concatMap(o -> {
                hasCalculated = true;
                // returning Observable.just(null) makes the test pass
                return Observable.empty();
            });
        }
    }
}

I've tried to simplify the "bad" observable further, but can't find anything I can remove to make it simpler.

My current understanding, though, is that it's an Observable which (regardless of how it's constructed), should emit a single value and then complete. We then make two similar instances of an object based on that Observable, and call a method on those objects which consumes the Observable, makes a note of having done so, and then returns Observable.empty().

Can anyone explain why using this observable causes the test the fail (when using a simpler observable causes the test to pass)?

It's also possible to make the test pass by either serially calling calculate().toBlocking().subscribe() rather than using zip, or making calculate return Observable.just(null) instead. That makes some sense to me (zip won't subscribe to calc2 if calc1 is empty, since it in that case zip could never yield anything), but not complete sense (I don't understand why zip doesn't behave like that for a simpler version of badObservable - the calculate() methods still return empty, regardless of that input).

回答1:

If you zip an empty source with something, the operator detects it can't produce any value anymore and unsubscribes from all of its sources. There is a mix of zip and merge involved and merge takes unsubscription seriously: it doesn't emit the value 3 at all thus concatMap doesn't call the mapping function for the second source either.