Emit the first successfull result from Mono.first

Frank :

I have two Monos that either return a 404 or yield a result.

How can I combine these two Monos that

  • the result is emitted as soon as one the Monos completes successfully.
  • an Optional.empty is returned as soon as all Monos yield a 404 or face any other error?

I tried

      Mono<Result> mono0 = client.get()
          .uri(uri1)
          .retrieve()
          .bodyToMono(Result.class)
          .onErrorResume(e -> Mono.never());

      Mono<Result> mono1 = client.get()
          .uri(uri2)
          .retrieve()
          .bodyToMono(Result.class)
          .onErrorResume(e -> Mono.never());

      return Mono.first(mono0, mono1)
          .blockOptional()

The problem with this approach is that it never completes if both Monos yield an error...

I think I need something like OnErrorDetach ....!?

To make the problem clearer I created a TestCase:

import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;

import java.util.Optional;

import org.junit.jupiter.api.Test;

import reactor.core.publisher.Mono;

class DummyTest {

  public Optional<String> getResult(Mono<String> m1, Mono<String> m2) {
    return Mono.first(m1, m2)
        // DoSomethingHere ?!??!?
        .blockOptional();
  }

  @Test
  void testFirstSuccessfullAndSecondErrorMono() {
    Optional<String> result = getResult(Mono.just("Something"), Mono.error(new RuntimeException()));

    assertThat(result, is(Optional.of("Something")));
  }

  @Test
  void testSecondSuccessfullAndFirstErrorMono() {
    Optional<String> result = getResult(Mono.error(new RuntimeException()), Mono.just("Something"));

    assertThat(result, is(Optional.of("Something")));
  }

  @Test
  void testTwoErrorMonosYieldEmpty() {
    Optional<String> result =
        getResult(Mono.error(new RuntimeException()), Mono.error(new RuntimeException()));

    assertThat(result, is(Optional.empty()));
  }


}


Michael Berry :

The problem with Mono.first() is that it takes the first signal (not the first value), so you end up trying to delay the corresponding Mono forever to get it not to replay the completion signal. You instead want something to take the first value.

Instead of .onErrorResume(e -> Mono.never());, use .onErrorResume(e -> Mono.empty());. You can then use:

Flux.merge(mono0, mono1).next();

The merge() (as oppose to concat()) combined with next() should ensure the first value is taken and the other ignored. You can of course still blockOptional() if you wish.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=183061&siteId=1