我有2个数据源:数据库(缓存)和api,我需要将它们组合成一个流。我知道我可以简单地使用concatArray或类似的东西,但是我想实现更复杂的行为:
可观察到的流,最多可发射2个元素。
它将在开始时同时订阅两个来源。
如果api调用足够快(<〜300ms),它将仅从中发出数据并完成流。
我用以下代码完成了它:
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,我认为这是不好的。但是通过这种方式,我可以全局访问发射器,这使我能够以自己想要的方式控制主流(发射数据,成功,错误)。
是否有更好的方法来实现这一目标?我很想看看一些代码示例。谢谢!
答案 0 :(得分:1)
可能是下面的代码可以完成这项工作。根据您的要求,我假设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()
运算符会很好用。
我希望这会有所帮助。
答案 1 :(得分:1)
另一个解决方案可能是这个(无主题):
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毫秒内(dbDuration -> timer(300, MILLISECONDS)
)没有从API服务发出任何消息,则发出来自数据库的实体(api.switchIfEmpty(db)
)。
如果api在300毫秒内发出某物,则该发出的仅是其Entity
(api.switchIfEmpty(.)
)。
这似乎也如您所愿...
答案 2 :(得分:1)
另一个更好的解决方案:
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服务出错,我们将从数据库中发出该项目。
似乎也可以按照您的意愿工作...