RxJs:如何使用.shareReplay()来缓存HTTP数据?

时间:2017-09-07 07:16:55

标签: angular rxjs

我一直在寻找一种在Angular中缓存HTTP数据的方法,最后我决定使用.shareReplay()来实现它。此外,我希望手动清除缓存或在ReplaySubject过期时清除缓存,因为我知道我可以设置.shareReplay()的持续时间。满足上述两个要求的最佳做法是什么?

4 个答案:

答案 0 :(得分:1)

您应该使用startsWith,因为这允许您使用本地缓存初始化数据,然后使用更新的数据替换。

答案 1 :(得分:1)

您不能真正使用shareReplay来缓存可观察的刷新缓存并同时提供初始缓存种子。

  • shareReplay可以使用ReplaySubject来缓存值
  • 要刷新缓存,您将需要一些外部控件来强制对基础可观察对象进行重新订阅并重置ReplaySubject
  • 可以使用startWith来提供初始值,但是存在在您只期望一个值的情况下发出多个值的问题。

我认为这足以与自定义运算符和缓存控制器配合使用。

const { defer } = require('rxjs/observable/defer');
const { AsyncSubject, Scheduler } = require('rxjs');

class Cache {
  get isExpired() {
    return this.scheduler.now() > this.expiration;
  }

  constructor(initialValue, maxTime = Number.POSITIVE_INFINITY, scheduler = Scheduler.queue) {
    this.isComplete = false;
    this.hasError = false;
    this.subject = null;
    this.maxTime = maxTime;
    this.scheduler = scheduler;
    this.expiration = Number.POSITIVE_INFINITY;

    if (typeof initialValue !== 'undefined') {
      this.subject = new AsyncSubject();
      this.subject.next(initialValue);
      this.subject.complete();
      this.isComplete = true;
      this.extendExpiration();
    }
  }

  extendExpiration() {
    this.expiration = this.scheduler.now() + this.maxTime;
  }

  preventExpiration() {
    this.expiration = Number.POSITIVE_INFINITY;
  }

  flush() {
    this.expiration = 0;
  }
}

function cachingOperator(cache = new Cache()) {
  let refCount = 0;
  let subscription;
  return function cacheOperation(source) {
    refCount++;
    if (!cache.subject || cache.hasError || cache.isExpired) {
      cache.hasError = false;
      cache.preventExpiration(); // prevent expiration before underlying observable completes
      cache.subject = new AsyncSubject();
      subscription = source.subscribe({
        next(value) { cache.subject.next(value); },
        error(err) {
          cache.hasError = true;
          cache.subject.error(err);
        },
        complete() {
          cache.isComplete = true;
          cache.extendExpiration();
          cache.subject.complete();
        },
      });
    }
    const innerSub = cache.subject.subscribe(this);
    return () => {
      refCount--;
      innerSub.unsubscribe();
      if (subscription && refCount === 0 && cache.isComplete) {
        subscription.unsubscribe();
      }
    };
  };
}

const caching = cache => source => source.lift(cachingOperator(cache));

Cache对象将保持状态。您的代码负责实例化,因此可以完全控制。

用法:

const myCache = new Cache({ result: 'initialValue' }, 1000); // initial value and cache time 1s

const request = httpService.get(url)
  .pipe(
    caching(myCache)
  );

request.subscribe(); // initial value

setTimeout(() => {
  request.subscribe(); // issues new request
  request.subscribe(); // cache request
  myCache.flush();
  request.subscribe(); // new request
}, 1100);

绝对可以改进API,使其更适合您的用例,但是我希望这为如何实现提供了粗略的想法。

答案 2 :(得分:0)

在寻找类似问题的解决方案时看到了这个问题。我无法使其仅与shareReplay api一起使用,但想共享一个仅限rxjs的解决方案(tw:inTypescript),以防有人使用它:

     private cache = {};
     private cacheProperty(key:string, prop:string, httpCall$:Observable<any>, expirationTime?: number): Observable<any>{
        if(!(key in this.cache && prop in this.cache[key])){
          const timer$ = timer(expirationTime)
          const clearCache = new Subject<void>()
          const clearCache$ = merge(timer$, clearCache.asObservable()).pipe(take(1))
          const cacheProp$ = httpCall$.pipe(shareReplay(1, 5000),takeUntil(clearCache$))
          clearCache$.subscribe(_=> delete this.cache[key][prop])
          this.cache[key] = Object.assign(!!this.cache[key] ? this.cache[key] : {} ,{[prop]:{call$:cacheProp$, clear:clearCache }})
        }
        return this.cache[key][prop].call
      }

      public clearPropCache(key:string, prop:string){
        this.cache[key][prop].clear.next()
      }

对于我们正在进行的项目,它是个性化的,它需要多个“用户”缓存(由键拆分)以及道具表示的多种类型的缓存(例如,bio)。您可以输入到期时间,也可以在第二个时间以空白主题手动清除它。

我们也使用了angular,所以httpCall $应该以从HttpClient模块返回的可观察形式出现,而没有在其他来源尝试过。

我还发现本文对于创建和缩小我的解决方案非常有帮助:https://blog.thoughtram.io/angular/2018/03/05/advanced-caching-with-rxjs.html

希望它可以帮助其他使用简单缓存解决方案的人!

答案 3 :(得分:0)

要与es6结合使用的节点版本,请切换到import而不是require语句。

在工厂,您可以用http请求替换Observable of(v)

我发布信息的可能性更大,而不是分享最先进的解决方案。

const {of} = require('rxjs');
const {shareReplay, tap} = require('rxjs/operators');

const BUFFER_SIZE = 1;
const CACHE_EXPIRATION = 10; // in ms

const factory = v => of(v).pipe(
    tap(v => console.log(v, 'pre processing')),
    shareReplay(BUFFER_SIZE, CACHE_EXPIRATION),
);

const cache = {};

const get = id => {
    let guard = false;

    if (cache.hasOwnProperty(id)) {
        cache[id].subscribe(v => {
            console.log(id, 'not expired');
            guard = true;
        });

        if (guard) {
            return cache[id];
        }

        console.log(id, 'expired');
    }

    console.log(id, 'creting new observble pipeline');
    cache[id] = factory(id);
    return cache[id];
};


get('1').subscribe(v => console.log(v, 'finall sub'));
setTimeout(() => get('1').subscribe(v => console.log(v, 'finall sub')), 5);
setTimeout(() => get('1').subscribe(v => console.log(v, 'finall sub')), 8);

输出:

1 creting new observble pipeline
1 pre processing
1 finall sub
1 not expired
1 finall sub
1 expired
1 creting new observble pipeline
1 pre processing
1 finall sub