让我们说我有以下事件:
DoSomething
做一些需要缓存数据的东西。当我触发事件时,我想查看缓存并在存在时执行某些操作。如果没有,那么我想获取它,等待它在缓存中,然后再试一次。
我想出了以下解决方案,但感觉我正在滥用map
运营商。 是否有更合适的操作符用于观察流并抛出错误或达到重试的效果?
const DO_SOMETHING = 'DO_SOMETHING';
const FETCH_SOMETHING = 'FETCH_SOMETHING';
const FETCH_SOMETHING_SUCCESSFUL = 'FETCH_SOMETHING_SUCCESSFUL';
const events = new Rx.Subject();
const cache = new Rx.BehaviorSubject(null);
// the magic sauce
const cacheInitializer = cache
// is there a better way than this?
.map(x => {
if (!x) { throw 'empty'; }
return x;
})
.retryWhen(x => {
console.log('Cache is empty');
events.next(FETCH_SOMETHING);
return events.filter(x => x === FETCH_SOMETHING_SUCCESSFUL)
.first();
});
// fake getting the data
events.filter(x => x === FETCH_SOMETHING)
.do(() => { console.log('Fetching data'); })
.delay(1000)
.subscribe(x => {
console.log('Data fetched and put into cache');
cache.next({ data: 1 });
events.next(FETCH_SOMETHING_SUCCESSFUL);
});
// handle doing something
events.filter(x => x === DO_SOMETHING)
.do(() => { console.log('Check the cache'); })
.switchMapTo(cacheInitializer)
.subscribe(x => {
console.log('Do something', x);
});
events.next(DO_SOMETHING);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.6/Rx.min.js"></script>
上下文:我正在使用ngrx和效果。 FETCH_SOMETHING
会触发另一种效果,FETCH_SOMETHING_SUCCESSFUL
会表示效果已成功完成。在这种情况下,我的缓存是ngrx。我不认为我会想要在API层中缓存,因为这仍然会导致我从缓存的响应中更新状态,而不仅仅是依赖状态中的数据。
答案 0 :(得分:2)
你想要一个简单的shareReplay
,但你需要rxjs 5.5.0或更高版本,因为已经修复了一个bug。
const cached$ = request$.shareReplay(1);
这将在第一次订阅时触发请求,但后续订阅将使用缓存值。
将错误传递给订阅者,然后销毁内部主题,以便错误本身不被缓存。这使得可观察的可重试性。因此,您可以附加所需的任何重试逻辑(例如重试直到成功)。
最后,如果refCount在某个时刻变为0,缓存也会持续存在。
shareReplay
还接受第二个参数,就像ReplaySubject
构造函数一样,定义了一个保留缓存的时间窗口。
// Faked request which sometimes errors
const request$ = Rx.Observable
.defer(() => Rx.Observable.of(Math.random()))
.do(() => console.log('API called'))
.map(val => {
if (val <= 0.3) {
console.log('API error');
throw val;
} else {
return val;
}
})
.delay(250);
const cached$ = request$.shareReplay(1);
Rx.Observable.timer(0, 1000)
.take(5)
.switchMap(() => cached$.retry(5))
.subscribe(console.log);
&#13;
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.6/Rx.min.js"></script>
&#13;
答案 1 :(得分:1)
缓存HTTP响应时,我使用以下内容:
let counter = 1;
const cache$ = new ReplaySubject(1, 1000);
// Mock HTTP request
const http$ = Observable
.defer(() => Observable.of(counter++).delay(100).do(val => cache$.next(val)))
.share();
const source$ = Observable
.merge(cache$, http$)
.take(1);
查看现场演示:https://jsbin.com/rogetem/9/edit?js,console
它仍然需要一个额外的变量cache$
。我相信没有它就可以实现这一点,但我认为我必须使用materialize()
和dematerialize()
,这让人很困惑。
当调用defer()
的回调时,您知道缓存为空。现在它只增加counter
。
请注意,我仍然必须使用.do(val => cache$.next(val))
仅将next
次通知传递给ReplaySubject
并避免error
和complete
,因为这会停止主题。
你可以看到它确实适用于例如:
source$.subscribe(val => console.log("Response 0:", val));
setTimeout(() => source$.subscribe(val => console.log("Response 50:", val)), 50);
setTimeout(() => source$.subscribe(val => console.log("Response 60:", val)), 60);
setTimeout(() => source$.subscribe(val => console.log("Response 200:", val)), 200);
setTimeout(() => source$.subscribe(val => console.log("Response 1200:", val)), 1200);
setTimeout(() => source$.subscribe(val => console.log("Response 1500:", val)), 1500);
setTimeout(() => source$.subscribe(val => console.log("Response 3500:", val)), 3500);
这将打印以下输出(每个值缓存1秒):
Response 0: 1
Response 50: 1
Response 60: 1
Response 200: 1
Response 1200: 2
Response 1500: 2
Response 3500: 3