简体   繁体   English

将Angular 4可观察的HTTP调用包装为一个可观察的缓存

[英]Wrapping Angular 4 Observable HTTP calls into one Observable for caching

I have an Angular 4 application, that acts as a dashboard of a system. 我有一个Angular 4应用程序,它充当系统的仪表板。 A number of different components call the same backing REST call through the same TypeScript service classes. 许多不同的组件通过相同的TypeScript服务类调用相同的支持REST调用。 While this works, I would like to avoid the unnecessary duplicated request storms hammering the server by introducing some caching service on the client side. 在此过程中,我想通过在客户端引入一些缓存服务来避免不必要的重复请求风暴,从而冲击服务器。

I have implemented something like this for my caching (in TypeScript), which is then used by my services, that pass the HTTP call in as computeFunction : 我已经为我的缓存(在TypeScript中)实现了类似的东西,然后供我的服务使用,该缓存将HTTP调用作为computeFunction传递给:

@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;
    });
  }

}

This works correctly, however, if calls with the same key are made in quick succession (eg when the application is starting), they will all fired against the server, as the cache does not have the return value at the time of the check. 这可以正常工作,但是,如果快速连续地使用相同的key进行调用(例如,当应用程序启动时),则它们都将针对服务器触发,因为在检查时缓存没有返回值。

So I think I should instead somehow implement my own cacheable wrapper "multiplexing" Observable , that can be returned to multiple cache callers, that 因此,我认为我应该以某种方式实现我自己的可缓存包装器“多路复用” Observable ,可以将其返回给多个缓存调用者,

  1. Executes the call passed in computeFunction only once 仅执行一次在computeFunction传递的调用
  2. Caches the return value 缓存返回值
  3. Returns the value to each of its subscribers, then cleans up itself like the HTTP Observable s do, so that you don't have to unsubscribe() . 将值返回给其每个订阅者,然后像HTTP Observable一样清理自身,这样您就不必unsubscribe()

Could someone please give me a sample on how to do this? 有人可以给我一个样本,怎么做吗?

The challenge is that the Observable should handle both cases, when 我们面临的挑战是, Observable应该处理这两种情况下,当

  • subscriptions made before the wrapped computeFunction returns. 包装的computeFunction返回之前进行的预订。 (wait till the wrapped Observable calls the subscription) and (等到包装Observable调用订阅),然后
  • subscriptions made after the wrapped computeFunction returned. 包装的computeFunction返回后进行的订阅。 (serve the cached value). (提供缓存的值)。

Or am heading into the wrong direction and getting the whole thing overcomplicated? 还是正朝着错误的方向发展并使整个事情变得过于复杂? If there is a simpler concept I could follow, I would be more grateful to learn it. 如果有一个更简单的概念可以遵循,我将不胜感激。

You don't need a lot of fancy logic. 您不需要很多花哨的逻辑。 You can just use shareReplay(1) to multicast the observable. 您可以只使用shareReplay(1)来多播可观察对象。 Here's an example: 这是一个例子:

// 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);

Now you can subscribe all you want: 现在,您可以订阅所有想要的内容:

// 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();

And all you will see is a single log statement. 您将看到的只是一个日志语句。

If you want a time-based expiration, you can get rid of the never() and instead do this: 如果您希望基于时间的到期日,可以摆脱never() ,而是这样做:

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

Note, though, that this is a hot stream that will query the API until all observers unsubscribe – so beware of memory leaks. 但是请注意,这是一个热流,它将查询API,直到所有观察者取消订阅为止-因此请注意内存泄漏。


On a small note, the Observable.never() trick will only work on very recent versions of rxjs due to this fixed bug . 值得注意的是,由于此固定的bugObservable.never()技巧仅适用于最新版本的rxjs。 The same goes for the timer-based solution. 基于计时器的解决方案也是如此。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM