如何在错误情况下组合多个非阻塞的RxJava链

时间:2017-06-20 03:00:07

标签: android rx-java

我的要求:

  • N并行调用
  • 等待所有通话完成(成功或失败)
  • 如果k(0 <= k null或错误对象,而成功的调用返回ResponseBody个对象。
  • 可选:如果RxJava有一个简单的方法来区分哪个调用是针对每个成功或失败的,那么很好,如果没有,我会解析响应并自己弄明白

我有什么:

        Observable<ResponseBody> api1Call = api1.fetchData();
        Observable<ResponseBody> api2Call = api2.fetchData();
        Observable<ResponseBody> api3Call = api3.fetchData();

        Observable.combineLatest(api1Call, api2Call, api3Call, new Func2<ResponseBody, ResponseBody, ResponseBody, Object>() {
            @Override
            public Object call(ResponseBody responseBody1, ResponseBody responseBody2, ResponseBody responseBody3) {
                Logger.i("what does this do? - '%s', '%s', '%s'", responseBody1, responseBody2, responseBody3);
                return null;
            }
        }).onErrorResumeNext(new Func1<Throwable, Observable<?>>() {
            @Override
            public Observable<?> call(Throwable throwable) {
                Logger.e(throwable, "some error with one of the apis?");
                return Observable.empty();
            }
        }).subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<Object>() {
                    @Override
                    public void onCompleted() {
                        Logger.i("onCompleted");
                    }

                    @Override
                    public void onError(Throwable e) {
                        Logger.e(e, "onError");
                    }

                    @Override
                    public void onNext(Object o) {
                        Logger.i("onNext " + o);
                    }
                });

我得到的输出:

some error with one of the apis?
// stacktrace of the error
onCompleted

我是RxJava的新手,非常困惑。我在StackOverflow上找到了一些答案,说zip做了类似的事情,但它更符合我的要求。我猜测其中一个“组合”操作符+正确的异常处理将做我需要的。到目前为止,我们很难弄清楚

我正在使用的版本:

compile 'io.reactivex:rxjava:1.3.0'
compile 'io.reactivex:rxandroid:1.2.1'
compile 'com.squareup.retrofit2:adapter-rxjava:2.3.0'

5 个答案:

答案 0 :(得分:6)

  1. 您无法通过combineLastzip实现并行,rxjava会在我的测试中按顺序执行并发出您的项目。

  2. 如果您的某个任务失败,则系统不会调用Func2#call,而是会提交onError。您甚至无法以这种方式获得其他成功任务的结果。

  3. 解决方案是flatMap,它是rxjava中实现并发的传统方式。它也符合您的其他要求。

  4. 这是一个小而完整的例子。

    我使用简单的网站服务进行测试。

    我使用Semaphore等待完成所有任务,您可以完全忽略它。我将日志记录添加到http请求以便更好地理解,您也可以完全忽略它。

    public interface WebsiteService {
    
        @GET
        Observable<ResponseBody> website(@Url String url);
    
    }
    

    然后我使用以下内容用rxjava测试结果。

       HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
        loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BASIC);
    
        Retrofit retrofit = new Retrofit.Builder().baseUrl("https://www.google.com")
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .client(new OkHttpClient.Builder().addInterceptor(loggingInterceptor).build())
                .build();
        WebsiteService websiteService = retrofit.create(WebsiteService.class);
    
        final Semaphore s = new Semaphore(1);
        try {
            s.acquire();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    
        Observable<ResponseBody> first = websiteService.website("http://github.com");
        Observable<ResponseBody> second = websiteService.website("http://stackoverflow.com");
        Observable<ResponseBody> third = websiteService.website("http://notexisting.com");
    
        final int numberOfCalls = 3; // testing for three calls
    
        Observable.just(first, second, third)
                .flatMap(new Function<Observable<ResponseBody>, ObservableSource<ResponseBody>>() {
                    @Override
                    public ObservableSource<ResponseBody> apply(@NonNull Observable<ResponseBody> responseBodyObservable) throws Exception {
                        return responseBodyObservable.subscribeOn(Schedulers.computation());
                    }
                })
                .subscribeOn(Schedulers.computation())
                .subscribe(new Observer<ResponseBody>() {
    
                    private int currentDoneCalls = 0;
    
                    private void checkShouldReleaseSemaphore() {
                        if (currentDoneCalls >= numberOfCalls) {
                            s.release();
                        }
                    }
    
                    @Override
                    public void onSubscribe(@NonNull Disposable d) {
    
                    }
    
                    @Override
                    public void onNext(@NonNull ResponseBody responseBody) {
                        System.out.println("Retrofit call success " + responseBody.contentType());
                        synchronized (this) {
                            currentDoneCalls++;
                        }
                        checkShouldReleaseSemaphore();
                    }
    
                    @Override
                    public void onError(@NonNull Throwable e) {
                        System.out.println("Retrofit call failed " + e.getMessage());
                        synchronized (this) {
                            currentDoneCalls++;
                        }
                        checkShouldReleaseSemaphore();
                    }
    
                    @Override
                    public void onComplete() {
                        System.out.println("onComplete, All request success");
                        checkShouldReleaseSemaphore();
                    }
    
                });
    
        try {
            s.acquire();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println("All request done");
            s.release();
        }
    

    我使用rxjava2并改进adapter-rxjava2进行测试。

    compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
    compile 'io.reactivex.rxjava2:rxjava:2.1.0'
    compile 'com.squareup.retrofit2:retrofit:2.3.0'
    compile 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
    compile 'com.squareup.okhttp3:logging-interceptor:3.8.1'
    

    更新

    来自github的RxJava2的介绍页面指出了实现并列论的实用方法。

      

    实际上,RxJava中的并行性意味着运行独立流并将其结果合并回单个流中。运营商flatMap执行此操作...

    虽然此示例基于RxJava2,但操作flatMap是  已存在于RxJava

答案 1 :(得分:1)

我认为在你的用例中,Zip运算符更合适

在这里你可以看到在主线程中运行,但如果你使用observerOn,它也可以让它在另一个线程中运行它们

/**
 * Since every observable into the zip is created to subscribeOn a different thread, it´s means all of them will run in parallel.
 * By default Rx is not async, only if you explicitly use subscribeOn.
  */
@Test
public void testAsyncZip() {
    scheduler = Schedulers.newThread();
    scheduler1 = Schedulers.newThread();
    scheduler2 = Schedulers.newThread();
    long start = System.currentTimeMillis();
    Observable.zip(obAsyncString(), obAsyncString1(), obAsyncString2(), (s, s2, s3) -> s.concat(s2)
                                                                                        .concat(s3))
              .subscribe(result -> showResult("Async in:", start, result));
}



private Observable<String> obAsyncString() {
    return Observable.just("")
                     .observeOn(scheduler)
                     .doOnNext(val -> {
                         System.out.println("Thread " + Thread.currentThread()
                                                              .getName());
                     })
                     .map(val -> "Hello");
}

private Observable<String> obAsyncString1() {
    return Observable.just("")
                     .observeOn(scheduler1)
                     .doOnNext(val -> {
                         System.out.println("Thread " + Thread.currentThread()
                                                              .getName());
                     })
                     .map(val -> " World");
}

private Observable<String> obAsyncString2() {
    return Observable.just("")
                     .observeOn(scheduler2)
                     .doOnNext(val -> {
                         System.out.println("Thread " + Thread.currentThread()
                                                              .getName());
                     })
                     .map(val -> "!");
}

您可以在此处查看更多示例https://github.com/politrons/reactive

答案 2 :(得分:1)

您可以使用Observable.mergeDelayError(api1Call, api2Call, api3Call)

Bonus :您还可以指定可以同时运行多少个最大并行调用。例如:

Observable .mergeDelayError(Observable.from(api1Call, api2Call, api3Call), 5)

答案 3 :(得分:0)

  1. 对于Retrofit中的多个并行调用,我们需要在初始化Retrofit时从OkHttp层设置它。 请检查this
  2. 如果在这种情况下使用combineLatestzip运算符(对于Retrofit调用),则每次调用只会发出一个项目。因此,两个运营商都将等待所有呼叫完成。所以,我们不需要担心这一点。有关详情,请查看combineLatestzip
  3. 如果你的意思是关于RxJava流错误的1 call fail,则会抛出此错误,不会发出任何组合项。但是1 call failhttp request fail,当3个调用完成时,流总是会发出一个项目。我们无法在此使用combineLastzip运算符。

答案 4 :(得分:0)

感谢@TinTran和this,这是正确的解决方案:
(我现在不能提出Retrofit Observables的确切语法,但这不重要,逻辑仍然是相同的改造)

Observable.mergeDelayError(getData1(), getData2()).doAfterTerminate(new Action0() {
            @Override
            public void call() {
                Logger.i("end of all streams");
                tvTheText.setText("all streams finished");
            }
        }).subscribe(new PrintSubscriber<>("merge" +
                " delay w error"));

观察者(改造者应该以同样的方式工作):

private Observable<String> getData1() {
        return Observable.create(new Observable.OnSubscribe<String>() {
            @Override
            public void call(Subscriber<? super String> singleSubscriber) {
                try {
                    long responseTime = 120 + new Random().nextInt(30);
                    Thread.sleep(responseTime);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                singleSubscriber.onNext("data 1");
                singleSubscriber.onCompleted();
            }
        }).observeOn(AndroidSchedulers.mainThread()).subscribeOn(Schedulers.io());
    }

    private Observable<String> getData2() {
        return Observable.create(new Observable.OnSubscribe<String>() {
            @Override
            public void call(Subscriber<? super String> singleSubscriber) {
                try {
                    long responseTime = 100 + new Random().nextInt(19);
                    Thread.sleep(responseTime);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                singleSubscriber.onError(new Exception());// this one never blocks the other Observables' streams
            }
        }).observeOn(AndroidSchedulers.mainThread()).subscribeOn(Schedulers.io());
    }

输出日志:

    10-24 15:27:23.335 D: ┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────
10-24 15:27:23.335 D: │ Thread: main
10-24 15:27:23.335 D: ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
10-24 15:27:23.335 D: │ SafeSubscriber.onNext  (SafeSubscriber.java:134)
10-24 15:27:23.335 D: │    PrintSubscriber.onNext  (PrintSubscriber.java:32)
10-24 15:27:23.335 D: ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
10-24 15:27:23.336 D: │ merge delay w error - onNext - data 1
10-24 15:27:23.336 D: └────────────────────────────────────────────────────────────────────────────────────────────────────────────────
10-24 15:27:23.342 V: ⇢ onError(e=java.lang.Exception)
10-24 15:27:23.342 V: ⇠ onError [0ms]
10-24 15:27:23.343 I: ┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────
10-24 15:27:23.343 I: │ Thread: main
10-24 15:27:23.343 I: ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
10-24 15:27:23.343 I: │ OperatorDoAfterTerminate$1.callAction  (OperatorDoAfterTerminate.java:73)
10-24 15:27:23.343 I: │    MainActivity$1.call  (MainActivity.java:37)
10-24 15:27:23.343 I: ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
10-24 15:27:23.344 I: │ end of all streams
10-24 15:27:23.344 I: └────────────────────────────────────────────────────────────────────────────────────────────────────────────────