簡體   English   中英

將數據庫和網絡調用與RxJava2結合

[英]Combine database and network call with RxJava2

我有2個數據源:數據庫(緩存)和api,我需要將它們組合成一個流。 我知道我可以簡單地使用concatArray或類似的東西,但是我想實現更復雜的行為:

  • 可觀測的流,最多將發射2個元素。

  • 一開始它將同時訂閱這兩個資源。

  • 如果api調用足夠快(<〜300ms),它將僅從中發出數據並完成流。

  • 如果api調用會很慢(>〜300ms),請從數據庫中發出數據,然后仍然等待api中的數據
  • 如果api調用無法成功,則從數據庫發出數據並發出錯誤。
  • 如果數據庫以某種方式比api慢,它將無法發出其數據(流完成解決了它)

我用以下代碼完成了它:

    public Observable<Entity> getEntity() {
    final CompositeDisposable disposables = new CompositeDisposable();
    return Observable.<Entity>create(emitter -> {
        final Entity[] localEntity = new Entity[1];

        //database call:
        disposables.add(database.getEntity()
                .subscribeOn(schedulers.io())
                .doOnSuccess(entity -> localEntity[0] = entity) //saving our entity because 
                                                        //apiService can emit error before 300 ms 
                .delay(300, MILLISECONDS)
                .subscribe((entity, throwable) -> {
                    if (entity != null && !emitter.isDisposed()) {
                        emitter.onNext(entity);
                    }
                }));

        //network call:
        disposables.add(apiService.getEntity()
                .subscribeOn(schedulers.io())
                .onErrorResumeNext(throwable -> {
                    return Single.<Entity>error(throwable) //we will delay error here
                            .doOnError(throwable1 -> {
                                if (localEntity[0] != null) emitter.onNext(localEntity[0]); //api error, emit localEntity
                            })
                            .delay(200, MILLISECONDS, true); //to let it emit localEntity before emitting error
                })
                .subscribe(entity -> {
                    emitter.onNext(entity); 
                    emitter.onComplete(); //we got entity from api, so we can complete the stream
                }, emitter::onError));
    })
            .doOnDispose(disposables::clear)
            .subscribeOn(schedulers.io());
}

代碼有點笨拙,我在這里在observable中創建了observables,我認為這很糟糕 但是通過這種方式,我可以全局訪問發射器,這使我能夠以自己想要的方式控制主流(發射數據,成功,錯誤)。

有更好的方法來實現這一目標嗎? 我很想看看一些代碼示例。 謝謝!

可能是下面的代碼可以完成這項工作。 根據您的要求,我假設api和數據庫處理Single<Entity>

private static final Object STOP = new Object();

public static void main(String[] args) {
    Database database = new Database(Single.just(new Entity("D1")));
    ApiService apiService = new ApiService(Single.just(new Entity("A1")));
    // ApiService apiService = new ApiService(Single.just(new Entity("A1")).delay(500, MILLISECONDS));
    // ApiService apiService = new ApiService(Single.error(new Exception("Error! Error!")));
    BehaviorSubject<Object> subject = BehaviorSubject.create();

    Observable.merge(
        apiService.getEntity()
                  .toObservable()
                  .doOnNext(t -> subject.onNext(STOP))
                  .doOnError(e -> subject.onNext(STOP))
                  .onErrorResumeNext(t ->
                                        Observable.concatDelayError(database.getEntity().toObservable(),
                                                                    Observable.error(t))),
        database.getEntity()
                .delay(300, MILLISECONDS)
                .toObservable()
                .takeUntil(subject)
    )
    .subscribe(System.out::println, 
               System.err::println);

    Observable.timer(1, MINUTES) // just for blocking the main thread
              .toBlocking()
              .subscribe();
}

由於出現以下情況,我無法刪除對Subject的使用:“如果數據庫以某種方式比api慢,它將無法發出其數據”和“如果api調用將是緩慢的(>〜300ms),則發出數據從數據庫中,仍然等待api中的數據”。 否則, amb()運算符會很好用。

我希望這有幫助。

另一個解決方案可能是這個(無主題):

public static void main(String[] args) throws InterruptedException {
    Database database = new Database(Single.just(new Entity("D1")));
    ApiService apiService = new ApiService(Single.just(new Entity("A1")));
    // ApiService apiService = new ApiService(Single.just(new Entity("A1")).delay(400, MILLISECONDS));
    // ApiService apiService = new ApiService(Single.error(new Exception("Error! Error!")));

    database.getEntity()
            .toObservable()
            .groupJoin(apiService.getEntity()
                                 .toObservable()
                                 .onErrorResumeNext(
                                    err -> Observable.concatDelayError(database.getEntity().toObservable(),
                                                                       Observable.error(err))),
                       dbDuration -> Observable.timer(300, MILLISECONDS),
                       apiDuration -> Observable.never(),
                       (db, api) -> api.switchIfEmpty(Observable.just(db)))
            .flatMap(o -> o)
            .subscribe(System.out::println,
                       Throwable::printStackTrace,
                       () -> System.out.println("It's the end!"));

    Observable.timer(1, MINUTES) // just for blocking the main thread
              .toBlocking()
              .subscribe();
}

如果在300毫秒內沒有從API服務發出任何信息( dbDuration -> timer(300, MILLISECONDS) ),則從數據庫發出實體( api.switchIfEmpty(db) )。

如果api在300毫秒內發出某物,則僅發出其Entityapi.switchIfEmpty(.) )。

這似乎也像您希望的那樣工作...

另一個更好的解決方案:

public static void main(String[] args) throws InterruptedException {
    Database database = new Database(Single.just(new Entity("D1")));
    ApiService apiService = new ApiService(Single.just(new Entity("A1")));
    // ApiService apiService = new ApiService(Single.just(new Entity("A1")).delay(400, MILLISECONDS));
    // ApiService apiService = new ApiService(Single.error(new Exception("Error! Error!")));

    Observable<Entity> apiServiceWithDbAsBackup =
            apiService.getEntity()
                      .toObservable()
                      .onErrorResumeNext(err -> 
                            Observable.concatDelayError(database.getEntity().toObservable(), Observable.error(err)));

    Observable.amb(database.getEntity()
                           .toObservable()
                           .delay(300, MILLISECONDS)
                           .concatWith(apiServiceWithDbAsBackup),
                   apiServiceWithDbAsBackup)
              .subscribe(System.out::println,
                         Throwable::printStackTrace,
                         () -> System.out.println("It's the end!"));

我們將amb()延遲使用,以使可觀察的數據庫采取將要發出的第一個數據庫。 如果api服務出錯,我們將從數據庫中發出該項目。 這似乎也像您希望的那樣工作...

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM