我们可以使用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请求,但第二次,因为我们使用缓存()运算符,请求不会被执行但我们将会被执行能够重用第一个结果。
第一个请求成功完成时,此方法正常。但是如果在第一次尝试中调用了onError,那么下次新订阅者订阅同一个observable时,将再次调用onError而不再尝试http请求。
我们要做的是,如果第一次调用onError,那么下次有人订阅同一个observable时,将从头开始尝试http请求。即,observable将仅缓存成功的api调用,即调用onCompleted的调用。
有关如何进行的任何想法?我们尝试使用retry()和cache()运算符并没有太多运气。
答案 0 :(得分:8)
嗯,对于任何仍然感兴趣的人,我认为我有一个更好的方法来实现它与rx。
关键注意事项是使用onErrorResumeNext,它可以让你在出错时替换Observable。 所以看起来应该是这样的:
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();
}
});
这样,如果第一个呼叫失败,未来的呼叫将只召回它(仅一次)。
但是尝试使用第一个observable的每个其他调用者都将失败并发出不同的请求。
你引用了原始的observable,让我们更新它。
所以,一个懒惰的吸气者:
Observable<Object> apiCall;
private Observable<Object> getCachedApiCall() {
if ( apiCall == null){
apiCall = createApiCallObservable().cache(1);
}
return apiCall;
}
现在,如果上一次失败将重试的吸气剂:
private Observable<Object> getRetryableCachedApiCall() {
return getCachedApiCall().onErrorResumeNext(new Func1<Throwable, Observable<? extends Object>>() {
public Observable<? extends Object> call(Throwable throwable) {
apiCall = null;
return getCachedApiCall();
}
});
}
请注意,每次调用它时只会重试一次。
所以现在你的代码看起来像这样:
---------------------------------------------
// 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);
答案 1 :(得分:6)
这是我们在扩展akarnokd解决方案后最终得到的解决方案:
public class OnErrorRetryCache<T> {
public static <T> Observable<T> from(Observable<T> source) {
return new OnErrorRetryCache<>(source).deferred;
}
private final Observable<T> deferred;
private final Semaphore singlePermit = new Semaphore(1);
private Observable<T> cache = null;
private Observable<T> inProgress = null;
private OnErrorRetryCache(Observable<T> source) {
deferred = Observable.defer(() -> createWhenObserverSubscribes(source));
}
private Observable<T> createWhenObserverSubscribes(Observable<T> source)
{
singlePermit.acquireUninterruptibly();
Observable<T> cached = cache;
if (cached != null) {
singlePermit.release();
return cached;
}
inProgress = source
.doOnCompleted(this::onSuccess)
.doOnTerminate(this::onTermination)
.replay()
.autoConnect();
return inProgress;
}
private void onSuccess() {
cache = inProgress;
}
private void onTermination() {
inProgress = null;
singlePermit.release();
}
}
我们需要缓存来自Retrofit的http请求的结果。所以这是创建的,其中一个可观察的内容会记住一个项目。
如果观察者在执行http请求时订阅了,我们希望它等待并且不执行两次请求,除非正在进行的请求失败。为此,信号量允许单个访问创建或返回高速缓存的observable的块,如果创建了新的observable,我们将等待,直到该终止。可以在here
找到上述测试答案 2 :(得分:4)
你必须做一些状态处理。我是这样做的:
public class CachedRetry {
public static final class OnErrorRetryCache<T> {
final AtomicReference<Observable<T>> cached =
new AtomicReference<>();
final Observable<T> result;
public OnErrorRetryCache(Observable<T> source) {
result = Observable.defer(() -> {
for (;;) {
Observable<T> conn = cached.get();
if (conn != null) {
return conn;
}
Observable<T> next = source
.doOnError(e -> cached.set(null))
.replay()
.autoConnect();
if (cached.compareAndSet(null, next)) {
return next;
}
}
});
}
public Observable<T> get() {
return result;
}
}
public static void main(String[] args) {
AtomicInteger calls = new AtomicInteger();
Observable<Integer> source = Observable
.just(1)
.doOnSubscribe(() ->
System.out.println("Subscriptions: " + (1 + calls.get())))
.flatMap(v -> {
if (calls.getAndIncrement() == 0) {
return Observable.error(new RuntimeException());
}
return Observable.just(42);
});
Observable<Integer> o = new OnErrorRetryCache<>(source).get();
o.subscribe(System.out::println,
Throwable::printStackTrace,
() -> System.out.println("Done"));
o.subscribe(System.out::println,
Throwable::printStackTrace,
() -> System.out.println("Done"));
o.subscribe(System.out::println,
Throwable::printStackTrace,
() -> System.out.println("Done"));
}
}
它通过缓存一个完全成功的源并将其返回给每个人来工作。否则,(部分)失败的源将破坏缓存,下一个调用观察器将触发重新订阅。
答案 3 :(得分:0)
您是否考虑过使用AsyncSubject为网络请求实施缓存?我做了一个示例应用程序RxApp来测试它是如何工作的。我使用单例模型来获取网络响应。这使得缓存响应,从多个片段访问数据,订阅待处理请求以及为自动UI测试提供模拟数据成为可能。
答案 4 :(得分:0)
柏拉图的解决方案随处可见!如果有人需要带有扩展功能和参数化的缓存大小的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
进行一次快速测试以证明其工作原理:
@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
}