使用时初始化缓存

时间:2018-02-01 17:03:43

标签: javascript rxjs

让我们说我有以下事件:

  • DoSomething的
  • FetchSomething
  • FetchSomethingSuccessful

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层中缓存,因为这仍然会导致我从缓存的响应中更新状态,而不仅仅是依赖状态中的数据。

2 个答案:

答案 0 :(得分:2)

你想要一个简单的shareReplay,但你需要rxjs 5.5.0或更高版本,因为已经修复了一个bug。

const cached$ = request$.shareReplay(1);

这将在第一次订阅时触发请求,但后续订阅将使用缓存值。

将错误传递给订阅者,然后销毁内部主题,以便错误本身不被缓存。这使得可观察的可重试性。因此,您可以附加所需的任何重试逻辑(例如重试直到成功)。

最后,如果refCount在某个时刻变为0,缓存也会持续存在。

shareReplay还接受第二个参数,就像ReplaySubject构造函数一样,定义了一个保留缓存的时间窗口。

&#13;
&#13;
// 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;
&#13;
&#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并避免errorcomplete,因为这会停止主题。

你可以看到它确实适用于例如:

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