Rx java 可观察,错误时重试,仅在完成时缓存

Rx java 可观察,错误时重试,仅在完成时缓存,rx-java,rx-android,Rx Java,Rx Android,我们可以使用cache()运算符避免多次执行长任务(http请求),并重用其结果: Observable apiCall = createApiCallObservable().cache(); // notice the .cache() --------------------------------------------- // the first time we need it apiCall.andSomeOtherStuff() .subscribe

我们可以使用cache()运算符避免多次执行长任务(http请求),并重用其结果:

Observable apiCall = createApiCallObservable().cache(); // notice the .cache()

---------------------------------------------
// the first time we need it
apiCall.andSomeOtherStuff()
               .subscribe(subscriberA);

---------------------------------------------
//in the future when we need it again
apiCall.andSomeDifferentStuff()
               .subscribe(subscriberB);
第一次执行http请求,但是第二次,因为我们使用了cache()操作符,请求将不会执行,但是我们可以重用第一个结果

当第一个请求成功完成时,这种方法可以正常工作。但是,如果在第一次尝试中调用了onError,那么下次新订阅者订阅相同的可观察对象时,将再次调用onError,而不会再次尝试http请求

我们试图做的是,如果第一次调用onError,那么下次有人订阅同一个可观察对象时,就会从头开始尝试http请求。可观察对象将只缓存成功的api调用,即那些调用了onCompleted的api调用


关于如何进行有什么想法吗?我们尝试使用retry()和cache()运算符,但运气不佳。

您必须执行一些状态处理。以下是我的做法:

公共类缓存尝试{
公共静态最终类OnErrorRetryCache{
最终原子引用缓存=
新原子引用();
最终可观察结果;
公共OnErrorRetryCache(可观测源){
结果=可观察。延迟(()->{
对于(;;){
Observable conn=cached.get();
如果(conn!=null){
返回连接;
}
可观测下一个=源
.doon错误(e->cached.set(null))
.replay()
.autoConnect();
if(cached.compareAndSet(null,next)){
下一步返回;
}
}
});
}
公共可观测get(){
返回结果;
}
}
公共静态void main(字符串[]args){
AtomicInteger调用=新的AtomicInteger();
可观测源=可观测
.just(1)
.doOnSubscribe(()->
System.out.println(“订阅:”+(1+调用.get()))
.flatMap(v->{
if(调用.getAndIncrement()==0){
返回Observable.error(新的RuntimeException());
}
可观察到的回报。仅(42);
});
Observable o=新的OneErrorRetryCache(source.get();
o、 订阅(System.out::println,
可丢弃::printStackTrace,
()->System.out.println(“完成”);
o、 订阅(System.out::println,
可丢弃::printStackTrace,
()->System.out.println(“完成”);
o、 订阅(System.out::println,
可丢弃::printStackTrace,
()->System.out.println(“完成”);
}
}

它的工作原理是缓存一个完全成功的源代码并将其返回给所有人。否则,一个(部分)发生故障的源将刷新缓存,下一个呼叫观察者将触发重新订阅。

这是我们在扩展akarnokd的解决方案后最终得到的解决方案:

公共类OnErrorRetryCache{
公共静态可观测源(可观测源){
返回新的OnErrorRetryCache(源)。延迟;
}
私人最终可观察延期;
私有最终信号量singlePermit=新信号量(1);
私有可观察缓存=null;
私有可观测inProgress=null;
专用OnErrorRetryCache(可观测源){
延迟=可观察。延迟(()->createWhenObserverSubscribes(源));
}
私有可观测createWhenObserverSubscribes(可观测源)
{
singlePermit.AcquireUnterrupty();
可观察缓存=缓存;
如果(缓存!=null){
singlePermit.release();
返回缓存;
}
inProgress=源
.doOnCompleted(此::onSuccess)
.doOnTerminate(此::onTermination)
.replay()
.autoConnect();
返回进程;
}
私有void onSuccess(){
cache=inProgress;
}
私有无效终止(){
inProgress=null;
singlePermit.release();
}
}
我们需要缓存来自改造的http请求的结果。所以这是创造出来的,一个可以观察到的东西在脑海中发出一个单一的东西


如果一个观察者在执行http请求时进行了订阅,我们希望它等待,而不是执行两次请求,除非正在进行的请求失败。为此,信号量允许对创建或返回缓存的可观察对象的块进行单一访问,如果创建了新的可观察对象,我们将等待该块终止。您是否考虑过使用AsyncSubject为网络请求实现缓存?我制作了一个示例应用程序来测试它是如何工作的。我使用单例模型从网络中得到响应。这使得缓存响应、访问来自多个片段的数据、订阅挂起的请求以及为自动化UI测试提供模拟数据成为可能。

好吧,对于仍然感兴趣的任何人,我认为我有一个更好的方法用rx实现它

关键的注意事项是使用OneRorResumeNext,这将允许您在出现错误时替换可观察对象。 所以它应该是这样的:

Observable<Object> apiCall = createApiCallObservable().cache(1);
//future call
apiCall.onErrorResumeNext(new Func1<Throwable, Observable<? extends Object>>() {
    public Observable<? extends Object> call(Throwable throwable) {
        return  createApiCallObservable();
        }
    });
---------------------------------------------
// the first time we need it - this will be without a retry if you want..
getCachedApiCall().andSomeOtherStuff()
               .subscribe(subscriberA);

---------------------------------------------
//in the future when we need it again - for any other call so we will have a retry
getRetryableCachedApiCall().andSomeDifferentStuff()
               .subscribe(subscriberB);

柏拉图的解决方案是正确的!如果有人需要一个带有扩展函数和参数化缓存大小的Kotlin版本,这里就是

class OnErrorRetryCache<T> constructor(source: Flowable<T>, private val retries: Int? = null) {

val deferred: Flowable<T>
private val singlePermit = Semaphore(1)

private var cache: Flowable<T>? = null
private var inProgress: Flowable<T>? = null

init {
    deferred = Flowable.defer { createWhenObserverSubscribes(source) }
}

private fun createWhenObserverSubscribes(source: Flowable<T>): Flowable<T> {
    singlePermit.acquireUninterruptibly()

    val cached = cache
    if (cached != null) {
        singlePermit.release()
        return cached
    }

    inProgress = source
            .doOnComplete(::onSuccess)
            .doOnTerminate(::onTermination)
            .let {
                when (retries) {
                    null -> it.replay()
                    else -> it.replay(retries)
                }
            }
            .autoConnect()

    return inProgress!!
}

private fun onSuccess() {
    cache = inProgress
}

private fun onTermination() {
    inProgress = null
    singlePermit.release()
}

}

fun <T> Flowable<T>.onErrorRetryCache(retries: Int? = null) = OnErrorRetryCache(this, retries).deferred

谢谢阿卡诺克,这看起来不错。只有当源是一个长时间运行的http请求(几秒钟)并且第二个、第三个订阅者在第一个订阅者仍在进行时订阅时,我才会遇到一些问题。在这种情况下,它们都会失败,并且不会尝试请求
---------------------------------------------
// the first time we need it - this will be without a retry if you want..
getCachedApiCall().andSomeOtherStuff()
               .subscribe(subscriberA);

---------------------------------------------
//in the future when we need it again - for any other call so we will have a retry
getRetryableCachedApiCall().andSomeDifferentStuff()
               .subscribe(subscriberB);
class OnErrorRetryCache<T> constructor(source: Flowable<T>, private val retries: Int? = null) {

val deferred: Flowable<T>
private val singlePermit = Semaphore(1)

private var cache: Flowable<T>? = null
private var inProgress: Flowable<T>? = null

init {
    deferred = Flowable.defer { createWhenObserverSubscribes(source) }
}

private fun createWhenObserverSubscribes(source: Flowable<T>): Flowable<T> {
    singlePermit.acquireUninterruptibly()

    val cached = cache
    if (cached != null) {
        singlePermit.release()
        return cached
    }

    inProgress = source
            .doOnComplete(::onSuccess)
            .doOnTerminate(::onTermination)
            .let {
                when (retries) {
                    null -> it.replay()
                    else -> it.replay(retries)
                }
            }
            .autoConnect()

    return inProgress!!
}

private fun onSuccess() {
    cache = inProgress
}

private fun onTermination() {
    inProgress = null
    singlePermit.release()
}

}

fun <T> Flowable<T>.onErrorRetryCache(retries: Int? = null) = OnErrorRetryCache(this, retries).deferred
@Test
fun `when source fails for the first time, new observables just resubscribe`() {

    val cacheSize = 2
    val error = Exception()
    var shouldFail = true //only fail on the first subscription

    val observable = Flowable.defer {
        when (shouldFail) {
            true -> Flowable.just(1, 2, 3, 4)
                    .doOnNext { shouldFail = false }
                    .concatWith(Flowable.error(error))
            false -> Flowable.just(5, 6, 7, 8)
        }
    }.onErrorRetryCache(cacheSize)

    val test1 = observable.test()
    val test2 = observable.test()
    val test3 = observable.test()

    test1.assertValues(1, 2, 3, 4).assertError(error) //fails the first time
    test2.assertValues(5, 6, 7, 8).assertNoErrors() //then resubscribes and gets whole stream from source
    test3.assertValues(7, 8).assertNoErrors() //another subscriber joins in and gets the 2 last cached values

}