正确使用Retrofit + RxJava的combineLatest

时间:2015-05-12 18:36:42

标签: retrofit rx-java

我想异步执行2次网络调用 - 我正在使用Retrofit + RxJava来完成此任务。这个逻辑来自一个简单的Runner类来测试解决方案。注意:这主要涉及服务器端的RxJava。

我的代码如下所示:

public static void main(String[] args) throws Exception {
  Api api = ...;

  Observable.combineLatest(
      api.getStates(),
      api.getCmsContent(),
      new Func2<List<States>, CmsContent, String>() {
        @Override public String call(List<State> states, CmsContent content) {
          ...
          return "PLACEHOLDER";
        }
      })
      .observeOn(Schedulers.immediate())
      .subscribeOn(Schedulers.immediate())
      .subscribe(new Observer<String>() {
        @Override public void onCompleted() {
          System.out.println("COMPLETED");
        }

        @Override public void onError(Throwable e) {
          System.out.println("ERROR: " + e.getMessage());
        }

        @Override public void onNext(String s) {
          // I don't care what's returned here
        }
      });
}

三个问题:

  1. Observable.combineLatest是您想要异步执行多个REST调用并在所有调用完成后继续使用的最佳运算符吗?
  2. 我的Func2实施目前返回String。执行2个API调用后,我将在Func2#call()方法中处理结果。我不在乎返回什么 - 必须有一个更好的方法来处理这个问题 - 我是否正确?
  3. 使用上面的代码正确执行API调用。但是,当我运行程序时,main方法无法使用正确的Process finished with exit code 0完成。可能导致代码挂起的原因是什么?
  4. 更新 - 2015-05-14

    根据建议,我已将逻辑更改为以下内容:

    public static void main(String[] args) throws Exception {
      Api api = ...;
    
      Observable.zip(
          api.getStates(),
          api.getCmsContent(),
          new Func2<List<States>, CmsContent, Boolean>() {
            @Override public Boolean call(List<State> states, CmsContent content) {
              // process data
              return true;
            }
          })
          .subscribeOn(Schedulers.io())
          .toBlocking()
          .first();
    }
    

    这看起来像我正在寻找的解决方案。我将使用它一段时间,看看我是否遇到任何麻烦。

3 个答案:

答案 0 :(得分:5)

1)如果您知道两条路径上都有一个值,那么它就像zip一样好。

2)你想做什么?您将在Func2中获得这对价值,如果您真的不关心使用onNext旅行的内容,请返回您选择的价值。

3)Schedulers.immediate()在某种意义上不是真正的调度程序,并且很容易出现相同的池死锁情况。你真的不需要使用它。如果要在完成异步工作之前阻止主线程,请使用toBlocking().first()作为示例。

答案 1 :(得分:1)

1)不是最好使用zip()。如果两个(或更多)api中的一个返回“慢”不同的结果/它具有缓存的本质,那么组合最新是好的。

2)Fun2有助于合并结果。在onNext()或onError()中处理结果更好(在架构方面)。您可以使用简单的Pair<T,Y>类将结果从Func2传递给onNext()。

3)没有错。所述结果应在onNext()中处理,而不是在onComplete中处理。根据{{​​3}},结果仅在onNext()中传递(当然是corectly)。

希望那些帮助。

答案 2 :(得分:1)

我意识到我迟到了一年,但OP在2015-05-14发布的编辑不符合他原来的要求:

  

我想异步执行2次网络调用

  1. Observables getStatesgetCmsContent不会同时执行,除非他们单独订阅单独的线程。这是他的帖子中遗漏的一个关键点,之前的答案都没有提到它。

    Observable.fromCallable(() -> doStuff())
      .subscribeOn(Schedulers.computation());
    
  2. 正如@akarnokd所说,如果两个流都有单个值,zipcombineLatest的行为类似。合并函数将阻塞,直到getStatesgetCmsContent都返回,但就像我上面所示,它们中的每一个都在不同的线程上并发执行。

    1. 另一种解决方案取决于List<States>CmsContent到达时合并的能力。鉴于他的代码,显然有某种“数据持有者”(未显示),因为他返回的是Boolean,而不是合并的数据。在下文中,forEach同时执行。

      Observable.just(api.getStates(), api.getCmsContent())
      // subscribe on separate thread as shown previously
      .flatMap(this::buildObservable)
      .toBlocking()
      // executes concurrently
      .forEach(item -> {
          // merge into "data holder"          
      });
      
    2. 当然,这段代码存在不强类型的问题,因此可以选择。