使用 RxJS(和 Angular)临时缓存来自参数化请求的 HTTP 响应 [英] Temporarily caching HTTP responses from parametrised requests using RxJS (and Angular)

查看:44
本文介绍了使用 RxJS(和 Angular)临时缓存来自参数化请求的 HTTP 响应的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想缓存和过期来自例如的 HTTP 响应带有特定参数的 GET 请求.

用例示例:

假设我构建了一个这样的服务:

@Injectable()出口类产品服务{构造函数(私有 http:HttpClient){}getProduct$(id: string): Observable{return this.http.get(`${API_URL}/${id}`);}getProductA$(id: string): Observable{返回 this.getProduct$(id).pipe(//这里有一堆复杂的操作};}getProductB$(id: string): Observable{返回 this.getProduct$(id).pipe(//这里有一堆其他复杂的操作};}}

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

两个组件都加载在同一页面上,因此会发生两次订阅 = 对同一端点的两次 HTTP 请求 - 即使我们知道结果很可能是相同的.

我想简单地缓存 getProduct$ 的响应,但我也希望这个缓存很快过期,因为产品管理部门的 Margareth 将在 2 分钟内更改产品价格.

我尝试过但不起作用的方法:

基本上,我尝试使用 shareReplay 保留一个热观察值字典,窗口时间为 5 秒.我的假设是,如果(源)observable 完成或订阅数为 0,那么下一个订阅者将简单地重新触发 observable,但情况似乎并非如此.

private product$: { [productId: string]: Observable} = {};getProduct$(id: string): Observable{如果(this.product$[id]){返回 this.product$[id];}this.product$[id] = this.http.get(`${API_URL}/${id}`).管道(shareReplay(1, 5000),//5s 后过期));返回 this.product$[id];}

我想,我可以尝试在完成时使用 finalize 或 finally 从我的字典中删除 observable,但不幸的是,每次取消订阅时也会调用它们.

所以解决方案可能更复杂.

有什么建议吗?

解决方案

如果我理解正确,你想根据他们的 id 参数缓存响应,所以当我制作两个 getProduct() 使用不同的 id 我会得到两个不同的未缓存结果.

我认为最后一个变体几乎是正确的.您希望它取消订阅其父级,以便稍后重新订阅和刷新缓存值.

shareReplay 操作符在 RxJS 5.5 之前的工作方式有所不同,如果我没记错的话,shareReplay 发生了变化并且它没有重新订阅其源代码.它后来在 RxJS 6.4 中重新实现,您可以根据传递给 shareReplay 的配置对象修改其行为.由于您使用的是 shareReplay(1, 5000) 似乎您使用的是 RxJS <6.4 所以最好使用 publishReplay()refCount() 运算符.

私有缓存:Observable[] = {}getProduct$(id: string): Observable{如果 (!this.cache[id]) {this.cache[id] = this.http.get(`${API_URL}/${id}`).pipe(发布重播(1, 5000),refCount(),采取(1),);}返回 this.cache[id];}

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

我希望这一切都有意义:)

I would like to cache and expire the HTTP responses from e.g. a GET request with certain params.

An example of a use case:

Suppose I build a service like this:

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

}

Now for whatever reason, function A is called in component A and function B is called in component B. I know this could be done in another way (such as a top-level smart component getting the HTTP data and passing it through input params), but for whatever reason, the two components are "smart" and they each call a service function.

Both components are loaded on the same page, so two subscriptions happen = two HTTP requests to the same endpoint - even though we know that the result will most likely be the same.

I want to simply cache the response of getProduct$, BUT I also want this cache to expire pretty quickly, because in 2 minutes Margareth from product management is going to change the product price.

What I tried but doesn't work:

Basically, I tried keeping a dictionary of hot observables using shareReplay with a window time of 5s. My assumption was that if the (source) observable completes or the number of subscriptions is 0, then the next subscriber would simply refire the observable, but this does not seem to be the case.

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];
}

I thought, I can try to remove the observable from my dictionary on completion using finalize or finally, but unfortunately those are also called on every unsubscribe.

So the solution must be perhaps more complicated.

Any suggestions?

解决方案

If I understand you correctly you want to cache responses based on their id parameter so when I make two getProduct() with different ids I'll get two different uncached results.

I think the last variant is almost correct. You want it to unsubscribe from its parent so it can re-subscribe and refresh the cached value later.

The shareReplay operator worked differently until RxJS 5.5 if I recall this correctly where shareReplay has changed and it didn't re-subscribe to its source. It was later reimplemented in RxJS 6.4 where you can modify its behavior based on a config object passed to shareReplay. Since you're using shareReplay(1, 5000) it seems like you're using RxJS <6.4 so it's better to use publishReplay() and refCount() operators instead.

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];
}

Notice that I also included take(1). That's because I want the chain to complete immediately after publishReplay emits its buffer and before it subscribes to its source Observable. It's not necessary to subscribe to its source because we just want to use the cached value. After 5s the cached value is discarded and publishReplay will subscribe to its source again.

I hope this all makes sense :).

这篇关于使用 RxJS(和 Angular)临时缓存来自参数化请求的 HTTP 响应的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆