Angular/RxJS 6:如何防止重复的 HTTP 请求? [英] Angular/RxJS 6: How to prevent duplicate HTTP requests?

查看:57
本文介绍了Angular/RxJS 6:如何防止重复的 HTTP 请求?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当前有一个场景,其中共享服务中的一个方法被多个组件使用.此方法对端点进行 HTTP 调用,该端点将始终具有相同的响应并返回一个 Observable.是否可以与所有订阅者共享第一个响应以防止重复的 HTTP 请求?

Currently have a scenario where a method within a shared service is used by multiple components. This method makes an HTTP call to an endpoint that will always have the same response and returns an Observable. Is it possible to share the first response with all subscribers to prevent duplicate HTTP requests?

以下是上述场景的简化版本:

Below is a simplified version of the scenario described above:

class SharedService {
  constructor(private http: HttpClient) {}

  getSomeData(): Observable<any> {
    return this.http.get<any>('some/endpoint');
  }
}

class Component1 {
  constructor(private sharedService: SharedService) {
    this.sharedService.getSomeData().subscribe(
      () => console.log('do something...')
    );
  }
}

class Component2 {
  constructor(private sharedService: SharedService) {
    this.sharedService.getSomeData().subscribe(
      () => console.log('do something different...')
    );
  }
}

推荐答案

根据您的简化方案,我构建了一个工作示例,但有趣的部分是了解正在发生的事情.

Based on your simplified scenario, I've built a working example but the interesting part is understanding what's going on.

首先,我构建了一个服务来模拟 HTTP 并避免进行真正的 HTTP 调用:

First of all, I've built a service to mock HTTP and avoid making real HTTP calls:

export interface SomeData {
  some: {
    data: boolean;
  };
}

@Injectable()
export class HttpClientMockService {
  private cpt = 1;

  constructor() {}

  get<T>(url: string): Observable<T> {
    return of({
      some: {
        data: true,
      },
    }).pipe(
      tap(() => console.log(`Request n°${this.cpt++} - URL "${url}"`)),
      // simulate a network delay
      delay(500)
    ) as any;
  }
}

Into AppModule 我已经替换了真正的 HttpClient 来使用模拟的:

Into AppModule I've replaced the real HttpClient to use the mocked one:

    { provide: HttpClient, useClass: HttpClientMockService }

现在,共享服务:

@Injectable()
export class SharedService {
  private cpt = 1;

  public myDataRes$: Observable<SomeData> = this.http
    .get<SomeData>("some-url")
    .pipe(share());

  constructor(private http: HttpClient) {}

  getSomeData(): Observable<SomeData> {
    console.log(`Calling the service for the ${this.cpt++} time`);
    return this.myDataRes$;
  }
}

如果从 getSomeData 方法返回一个新实例,您将拥有 2 个不同的可观察对象.无论您是否使用共享.所以这里的想法是准备"请求.CF myDataRes$.这只是请求,然后是 share.但它只声明一次并从 getSomeData 方法返回该引用.

If from the getSomeData method you return a new instance, you'll have 2 different observables. Whether you use share or not. So the idea here is to "prepare" the request. CF myDataRes$. It's just the request, followed by a share. But it's only declared once and returning that reference from the getSomeData method.

现在,如果您从 2 个不同的组件订阅 observable(服务调用的结果),您的控制台中将显示以下内容:

And now, if you subscribe from 2 different components to the observable (result of the service call), you'll have the following in your console:

Calling the service for the 1 time
Request n°1 - URL "some-url"
Calling the service for the 2 time

如您所见,我们对该服务进行了 2 次调用,但只发出了一个请求.

As you can see, we have 2 calls to the service, but only one request made.

是的!

如果你想确保一切都按预期工作,只需用 .pipe(share()) 注释掉该行:

And if you want to make sure that everything is working as expected, just comment out the line with .pipe(share()):

Calling the service for the 1 time
Request n°1 - URL "some-url"
Calling the service for the 2 time
Request n°2 - URL "some-url"

但是……这远非理想.

进入模拟服务的 delay 很酷,可以模拟网络延迟.但也隐藏了一个潜在的错误.

The delay into the mocked service is cool to mock the network latency. But also hiding a potential bug.

从 stackblitz repro,转到组件 second 并取消对 setTimeout 的注释.它会在 1 秒后调用服务.

From the stackblitz repro, go to component second and uncomment the setTimeout. It'll call the service after 1s.

我们注意到,现在,即使我们使用服务中的 share,我们也有以下内容:

We notice that now, even if we're using share from the service, we have the following:

Calling the service for the 1 time
Request n°1 - URL "some-url"
Calling the service for the 2 time
Request n°2 - URL "some-url"

为什么?因为当第一个组件订阅 observable 时,由于延迟(或网络延迟),500ms 内什么也没有发生.所以订阅在这段时间内仍然有效.一旦 500 毫秒延迟完成,observable 就完成了(它不是一个长期存在的 observable,就像一个 HTTP 请求只返回一个值,这个也是因为我们使用了 of).

Why that? Because when the first component subscribe to the observable, nothing happens for 500ms due to the delay (or the network latency). So the subscription is still alive during that time. Once the 500ms delay is done, the observable is completed (it's not a long lived observable, just like an HTTP request returns only one value, this one too because we're using of).

但是share 只不过是一个publishrefCount.Publish 允许我们多播结果,而 refCount 允许我们在没有人监听 observable 时关闭订阅.

But share is nothing more than a publish and refCount. Publish allows us to multicast the result, and refCount allows us to close the subscription when nobody is listening to the observable.

因此,对于您的使用共享的解决方案,如果您的某个组件的创建时间晚于制作时间第一个请求,您还有另一个请求.

So with your solution using share, if one of your component is created later than it takes to make the first request, you'll still have another request.

为了避免这种情况,我想不出任何出色的解决方案.使用多播我们必须然后使用连接方法,但究竟在哪里?做一个条件和一个计数器来知道它是否是第一次调用?感觉不太对.

To avoid that, I cannot think any brilliant solution. Using multicast we'd have to then use the connect method, but where exactly? Making a condition and a counter to know whether it's the first call or not? Doesn't feel right.

所以这可能不是最好的主意,如果有人能在那里提供更好的解决方案,我会很高兴,但与此同时,我们可以做些什么来保持可观察的活跃":

So it's probably not the best idea and I'd be glad if someone can provide a better solution there, but in the meantime here's what we can do to keep the observable "alive":

      private infiniteStream$: Observable<any> = new Subject<void>().asObservable();
      
      public myDataRes$: Observable<SomeData> = merge(
        this
          .http
          .get<SomeData>('some-url'),
        this.infiniteStream$
      ).pipe(shareReplay(1))

由于infiniteStream$ 从未关闭,并且我们正在合并两个结果并使用shareReplay(1),我们现在得到了预期结果:

As the infiniteStream$ is never closed, and we're merging both results plus using shareReplay(1), we now have the expect result:

即使对服务进行多次调用,也会调用一次 HTTP.无论第一个请求需要多长时间.

One HTTP call even if multiple calls are made to the service. No matter how long the first request takes.

这是一个 Stackblitz 演示来说明所有这些:https://stackblitz.com/edit/angular-n9tvx7

Here's a Stackblitz demo to illustrate all of that: https://stackblitz.com/edit/angular-n9tvx7

这篇关于Angular/RxJS 6:如何防止重复的 HTTP 请求?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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