将Angular 4 Observable HTTP调用包装到一个Observable中进行缓存

时间:2017-12-13 12:26:30

标签: angular typescript caching rxjs observable

我有一个Angular 4应用程序,它充当系统的仪表板。 许多不同的组件通过相同的TypeScript服务类调用相同的后备REST调用。虽然这有效,但我想通过在客户端引入一些缓存服务来避免不必要的重复请求风暴锤击服务器。

我已经为我的缓存(在TypeScript中)实现了类似的东西,然后由我的服务使用,它以computeFunction的形式传递HTTP调用:

@Injectable()
export class CacheService {

  private cacheMap = {};


  getAsObservable<V>(
                key: string,
                expirationThresholdSeconds: number,
                computeFunction: () => Observable<V>): Observable<V> {

    const cacheEntry = this.cacheMap[key];

    if (...) {
      // if cached entry is valid, return it immediately

      return Observable.of<V>(cacheEntry.value);          
    } else {
      // if not found or expired, call the method, and use map(...) to store the returned value
      return computeFunction().map(returnValue => {

        const expirationTime = new Date().getTime() + (expirationThresholdSeconds * 1000);

        const newCacheEntry = ... // build cache entry with expiration set

        this.cacheMap[key] = newCacheEntry;

        return returnValue;
    });
  }

}

这是正常的,但是,如果快速连续进行具有相同key的调用(例如,当应用程序启动时),它们将全部触发服务器,因为缓存没有返回值在检查时。

所以我认为我应该以某种方式实现我自己的可缓存包装“多路复用”Observable,它可以返回到多个缓存调用者,

  1. 仅执行computeFunction中传递的调用
  2. 缓存返回值
  3. 返回每个订阅者的值,然后像HTTP Observable一样自行清理,这样您就不必unsubscribe()
  4. 有人可以给我一个如何做到这一点的样本吗?

    挑战是Observable应该处理两种情况,

    • 在换行computeFunction之前进行的订阅。 (等到包裹的Observable调用订阅)和
    • 在包装的computeFunction返回后进行的订阅。 (提供缓存的值)。

    或者我正朝着错误的方向前进,让整个过程变得过于复杂?如果我可以遵循一个更简单的概念,我会更加感激学习它。

1 个答案:

答案 0 :(得分:1)

你不需要很多花哨的逻辑。您可以使用shareReplay(1)来组播可观察对象。这是一个例子:

// Simulate an async call
// Side effect logging to track when our API call is actually made
const api$ = Observable.of('Hello, World!').delay(1000)
    .do(() => console.log('API called!'));

const source$ = api$
     // We have to make sure that the observable doesn't complete, otherwise
     // shareReplay() will reconnect if the ref count goes to zero
     // in the mean time. You can leave this out if you do actually
     // want to "invalidate the cache" if at some point all observers
     // have unsubscribed.
    .concat(Observable.never())
     // Let the magic happen!
    .shareReplay(1);

现在您可以订阅所需的一切:

// Two parallel subscriptions
const sub1 = source$.subscribe();
const sub2 = source$.subscribe();

// A new subscription when ref count is > 0
sub1.unsubscribe();
const sub3 = source$.subscribe();

// A new subscription after ref count went to 0
sub2.unsubscribe();
sub3.unsubscribe();
const sub4 = source$.subscribe();
sub4.unsubscribe();

所有你会看到的只是一个日志声明。

如果你想要一个基于时间的过期,你可以摆脱never()而不是这样做:

const source$ = Observable.timer(0, expirationTimeout)
    .switchMap(() => api$)
    .shareReplay(1);

请注意,这是一个热流,它将查询API,直到所有观察者都取消订阅 - 所以要小心内存泄漏。

简而言之,Observable.never()技巧仅适用于this fixed bug的最新版本的rxjs。基于计时器的解决方案也是如此。