1

I have to call an external API method several times a row, until I either run out of attempts, or receive a positive response. In case I run out of attempts, I still want to use (for logging/diagnostics purposes) the latest unsuccesful response. Currently I do it like this:

        class SimpleWrapper<T> {
        private T data;

        public T getData() {
            return data;
        }

        public void setData(T data) {
            this.data = data;
        }
    }

    SimpleWrapper<String> state = new SimpleWrapper<>();

    Observable<Response> getLogObservable = Observable.interval(checkInterval, TimeUnit.SECONDS)
            .take(checkCount)
            .flatMap(ignored -> api.check())
            .doOnNext(response -> {
                logger.info("Check response: {}", response.getMessage());
                state.setData(response.getMessage());
            })
            .firstOrDefault(null, HubLogsApiResponse::isSuccess)
            .flatMap(response -> {
                if (response == null) {
                    return Observable.error(new RuntimeException(String.format("Last call to check was unsuccessful: %s", state.getData())));
                } else {
                    return Observable.just(response);
                }
            })

This is ugly and requires me to use side-effects, but I don't know how else can this be achieved.

Any suggestions, may be?

62mkv
  • 1,444
  • 1
  • 16
  • 28

1 Answers1

0

Problem: make a series of requests and either return the first one that succeeds, or the last one as an error.

If we introduce a Pair, we can combine the count produced by interval with the API request. Then, the terminating condition can be checked without setting a global variable.

Observable<Response> getLogObservable = 
  Observable.interval(checkInterval, TimeUnit.SECONDS)
        .flatMap(requestNumber -> api.check()
                                    .map(response -> 
                                         new Pair(requestNumber+1, response))
        .flatMap(pair -> mapResponse( pair ) )
        .take( 1 );

Observable<Response> mapResponse( Pair<Long, Response> response ) {
  if ( response.getSecond().isSuccess() ) {
    return Observable.just( response.getSecond() );
  }
  if ( response.getFirst() >= checkCount ) {
    Observable.error(
      new RuntimeException(String.format("Last call to check was unsuccessful: %s", 
            pair.getSecond().getData())));
  }
  return Observable.empty();
}

mapResponse produces an error that includes the latest response only when the counter has run its course. Otherwise, it returns either a successful response or an empty observable.

Bob Dalgleish
  • 8,167
  • 4
  • 32
  • 42
  • I knew that tuples were worthwhile ! ) sadly JDK does not have them. anyway, as long as it does not require having side effects, I will accept this. Although I did not check if it's actually working! – 62mkv Feb 02 '18 at 06:17
  • You can construct a `Pair` class very easily with 10 lines of code. Don't get stuck on that part of it. – Bob Dalgleish Feb 02 '18 at 13:19
  • my comment was only due to the fact, that I already had a need for tuples in other projects and learned that they're not included in JDK (due to reasons explained, among others, here: in https://stackoverflow.com/a/24336841/2583044) – 62mkv Feb 05 '18 at 02:41