用于缓存和延迟数据检索的可观察模式

时间:2017-04-04 13:38:10

标签: angularjs caching rxjs observable rxjs5

我正在尝试使用RxJS Observable以角度创建缓存函数。最初我使用angularjs $q的延迟承诺创建了这个方法。 Observables和RxJS对我来说都是新手,我发现这种工作方法仍然有些令人困惑。

这是我当前对getOrCreate缓存功能的实现。从存储中检索密钥的单个值(this.get()),如果它不在那里,则在其他地方检索它(fetcher)。

假设fetcher是比this.get()更慢的数据源。当我们仍然从this.get()检索时,可能会触发对同一密钥的多个请求,因此我放入了一个聚合器:只为同一个密钥的多个请求创建了一个observable。

protected observableCache : {[key: string] : Observable<any>} = {};

get<T>(key : string): Observable<T> { /* Async data retrieval */ }

getOrCreate<T>(key : string, fetcher: () => Observable<T>) : Observable<T> {
  const keyHash = this.hash(key);

  // Check if an observable for the same key is already in flight
  if (this.observableCache[keyHash]) {
    return this.observableCache[keyHash];
  } else {

    let observable : Observable<T>;

    this.get(key).subscribe(

      // Cache hit
      (result) => { observable = Observable.of(result); },

      // Cache miss. Retrieving from fetching while creating entry
      () => {
        fetcher().subscribe((fetchedResult) => {
          if(fetchedResult) {
            this.put(key, fetchedResult);
          }
          observable = Observable.of(fetchedResult);
        });
      }
    );


   // Register and unregister in-flight observables
   this.observableCache[keyHash] = observable;
   observable.subscribe(()=> {
      delete this.observableCache[this.hash(key)];
   });

    return observable;
  }
}

这是我当前版本的代码,但看起来我没有正确处理异步代码:

  • Observable将在实例化之前返回:return observableobservable = Observable.of(result)之前触发;
  • this.get()仍处于投放状态时,可能会有更好的聚合所有同一密钥请求的模式。

有人可以帮忙找到应该使用Observer模式吗?

1 个答案:

答案 0 :(得分:3)

我认为这可能有效。改写为:

getOrCreate<T>(key : string, fetcher: () => Observable<T>) : Observable<T> {
    const keyHash = this.hash(key);

    // Check if an observable for the same key is already in flight
    if (this.observableCache[keyHash]) {
        return this.observableCache[keyHash];
    }

    let observable : ConnectableObservable<T> = this.get(key)
        .catch(() => { // Catch is for when the source observable throws  error: It replaces it with the new Rx.Observable that is returned by it's method
            // Cache miss. Retrieving from fetching while creating entry
            return this.fetchFromFetcher(key, fetcher);
        })
        .publish();

    // Register and unregister in-flight observables
    this.observableCache[keyHash] = observable;
    observable.subscribe(()=> {
        delete this.observableCache[keyHash];
    });
    observable.connect();

    return observable;
},

fetchFromFetcher(key : string, fetcher: () => Observable<T>) : Observable<T> {
    // Here we create a stream that subscribes to fetcher to use `this.put(...)`, returning the original value when done
    return Rx.Observable.create(observer => {
        fetcher().subscribe(fetchedResult => {
            this.put(key, fetchedResult);
            observer.next(fetchedResult);
        },
        err => observer.error(err),
        () => observer.complete())
    });
}

说明:

  1. Observable与承诺非常不同。他们要处理异步的东西,并且有一些相似之处,但它们是完全不同的
  2. 由于this.get(...)似乎是异步的,您的let observable不会被填充,直到它产生一个值,因此当您将其分配给缓存时,它是正常的。空。
  3. 关于observable(以及与promises的主要区别)的一个好处是你可以在执行任何事情之前定义一个流。在我的解决方案中,在调用observable.connect()之前,没有任何内容被调用。这避免了这么多.subscriptions
  4. 因此,在我的代码中,我获得了this.get(key)流,并告诉它如果它失败(.catch(...))它必须获取结果,但一旦获取该结果,则将其放入你的缓存(this.put(key, fetchedResult
  5. 然后我publish()这个可观察的:这使得它的行为更像是承诺,它使它变得更热&#34;。这意味着所有订阅者都将从同一个流中获取值,而不是每次订阅时都创建一个从0开始的新流。
  6. 然后我将它存储在可观察池中,并设置为在完成时将其删除。
  7. 最后,我.connect()。只有当你publish()它,实际订阅原始流,执行你想要的一切时,才会这样做。
  8. 要说清楚,因为这是来自Promises的常见错误,如果您将流定义为:

    let myRequest = this.http.get("http://www.example.com/")
        .map((result) => result.json());
    

    请求尚未发送。每次执行myRequest.subscribe()时,都会向服务器发出新请求,它不会重复使用&#34;第一个订阅&#34;结果。这就是.publish()非常有用的原因:当你调用.connect()时,它会创建一个触发请求的订阅,并将共享最后收到的结果(Observables支持流:很多结果)所有对已发布的observable的传入订阅。