使用RxJS(和Angular)临时缓存来自参数化请求的HTTP响应

时间:2019-03-01 15:39:43

标签: angular rxjs

我想缓存和终止来自例如带有某些参数的GET请求。

用例示例:

假设我建立这样的服务:

@Injectable()
export class ProductService {

  constructor(private http: HttpClient) {}

  getProduct$(id: string): Observable<Product> {
    return this.http.get<Product>(`${API_URL}/${id}`);
  }

  getProductA$(id: string): Observable<ProductA> {
    return this.getProduct$(id).pipe(
      // bunch of complicated operations here
    };
  }

  getProductB$(id: string): Observable<ProductB> {
    return this.getProduct$(id).pipe(
      // bunch of other complicated operations here
    };
  }

}

现在无论出于何种原因,在组件A中调用函数A,在组件B中调用函数B。我知道这可以通过另一种方式完成(例如,顶级智能组件获取HTTP数据并将其传递给输入参数),但是无论出于何种原因,这两个组件都是“智能”的,它们各自调用服务函数。

两个组件都加载在同一页面上,因此发生了两个预订=向同一端点的两个HTTP请求-尽管我们知道结果很可能是相同的。

我只想缓存getProduct$的响应,但是我也希望该缓存很快过期,因为产品管理部门的玛格丽特(Margareth)将在2分钟内更改产品价格。

我尝试过但不起作用的内容:

基本上,我尝试使用窗口时间为5s的shareReplay保留热门可观察词的字典。我的假设是,如果(源)可观察对象完成或订阅数为0,则下一个订阅者将简单地激发该可观察对象,但事实并非如此。

private product$: { [productId: string]: Observable<Product> } = {};

getProduct$(id: string): Observable<Product> {
  if (this.product$[id]) {
    return this.product$[id];
  }
  this.product$[id] = this.http.get<Product>(`${API_URL}/${id}`)
    .pipe(
      shareReplay(1, 5000), // expire after 5s
    )
  );
  return this.product$[id];
}

我想,我可以尝试使用finalize或finally在完成后从字典中删除可观察的对象,但不幸的是,每个取消订阅的对象也都会调用这些对象。

因此解决方案可能必须更复杂。

有什么建议吗?

1 个答案:

答案 0 :(得分:1)

如果我对您的理解正确,那么您希望根据其id参数来缓存响应,因此当我用不同的getProduct()来创建两个id时,我将得到两个不同的未缓存结果。 / p>

我认为最后一个变体几乎是正确的。您希望它退订其父级,以便以后可以重新订阅并刷新缓存的值。

如果我正确地记得shareReplay发生了更改并且没有重新订阅其来源,那么shareReplay运算符在RxJS 5.5之前的工作方式有所不同。后来在RxJS 6.4中重新实现了它,您可以在其中基于传递给shareReplay的配置对象来修改其行为。由于您使用的是shareReplay(1, 5000),似乎您使用的是RxJS <6.4,因此最好使用publishReplay()refCount()运算符。

private cache: Observable<Product>[] = {}

getProduct$(id: string): Observable<Product> {
  if (!this.cache[id]) {
    this.cache[id] = this.http.get<Product>(`${API_URL}/${id}`).pipe(
      publishReplay(1, 5000),
      refCount(),
      take(1),
    );
  }

  return this.cache[id];
}

请注意,我还包括了take(1)。这是因为我希望链在publishReplay发出缓冲区之后并在订阅其源Observable之前立即完成。不必订阅其源,因为我们只想使用缓存的值。 5秒后,缓存的值将被丢弃,publishReplay将再次订阅其源。

我希望这一切都说得通:)。