1

我正在使用 Rxjs 和 Angular 框架进行前端项目,我想从 api“api/data_processor_classlib.php....”获取 json 数据。在 HTML 中订阅了管道 this.webProtectionHTML$ 的三个部分。我不知道为什么管道 this.webProtectionHTML$ 发出了 3 次请求。是否有任何可能的解决方案只发送一个请求并更新 HTML 中的所有数据?谢谢。

HTML 代码:

    <div class="tr">
      <div class="align-left">Phishing & Other Frauds</div>
      <div class="align-right">{{ (webProtectionHTML$|async)?.phishing}}</div>
    </div>
    <div class="tr">
      <div class="align-left">Spam URLs</div>
      <div class="align-right">{{ (webProtectionHTML$|async)?.spamURLs}}</div>
    </div>
    <div class="tr">
      <div class="align-left">Malware Sites</div>
      <div class="align-right">{{ (webProtectionHTML$|async)?.malware}}</div>
    </div>

零件:

this.webProtectionHTML$ = this.dayService$
      .pipe(
        mergeMap((days: DaysPeriod // params added to request url) => this.httpClient.get(`api/data_processor_classlib.php....`//request url, { responseType: 'text' })),
        map((html: string) => {
          //get html code and find data return as json data
          let result = this.getWebProtectionData(html)
          return result
        }))

网络日志:

在此处输入图像描述

4

5 回答 5

5

先前的一些答案正确地指出,async管道的每次使用都会导致 http 请求 - 但不解释原因。事实上,异步管道本身并不是问题。这是因为您的 http 请求 observable 是“冷的”(如此处所述:https ://www.learnrxjs.io/learn-rxjs/concepts/rxjs-primer#what-is-an-observable )。

“冷”可观察意味着它只会在某些消费者订阅它时才开始做某事并发出值。此外,默认情况下,每个新订阅都会启动新的执行——即使对同一个 observable 的多个订阅是并行创建的。这正是您在代码中观察到的:每个异步管道分别订阅 observable。

有几种方法可以解决这个问题。

  1. 确保您只有一个订阅。@Michael D 的回答利用了这种方法。这是解决问题的一种很常见的方法。一个潜在的缺点是,以这种方式手动创建的订阅不会在组件被销毁时自动取消订阅。在您的示例中,这可能没什么大不了的(如果dayService$只发出一个值)。但是,如果组件在 http 请求完成之前被销毁,那么如果不编写一些额外的代码(涉及实现 ngOnDestroy 生命周期方法),则不会取消该 http 请求。changeDetector.markForCheck()另一个缺点 -如果您的组件使用 OnPush,您将需要手动调用。

  2. 使这个可观察到的“热”。“热”意味着异步操作已经启动,所有订阅者都将收到该操作的结果——无论有多少订阅者。正如@xdeepakv 所建议的那样,使用 .toPromise() 就可以做到这一点。请注意,promise 根本不可取消 - 因此您将无法取消此类请求。另一个缺点 - 它仅在您的 observable 发出单个值然后完成(例如单个 http 请求)时才有效。

  3. 您可以使用shareReplay({refCount: true})运算符来制作可观察的多播 - 这允许多个订阅者共享相同的结果。在这种情况下,您无需更改模板(可以有多个异步管道)并从异步管道中实现的自动取消订阅/http 请求取消中受益。

this.webProtectionHTML$ = dayService$.pipe(
  mergeMap(...),
  map(...),
  shareReplay({refCount: true}) // <- making it a multicast
)
于 2020-04-04T23:49:32.097 回答
1

它被调用了 3 次,因为每个async管道都会触发一个请求。相反,在这些情况下,您可以订阅组件并使用成员变量。然后,您可以取消订阅ngOnDestroy挂钩中的订阅以避免内存泄漏。尝试以下

控制器

private dayServiceSubscription: Subscription;
public webProtectionHTML: any;

ngOnInit() {
  this.dayServiceSubscription = this.dayService$
    .pipe(
      mergeMap((days: DaysPeriod) => this.httpClient.get(`api/data_processor_classlib.php....`//request url, { responseType: 'text' })),
      map((html: string) => this.getWebProtectionData(html)))
    .subscribe(response => this.webProtectionHTML = response);
}

ngOnDestroy() {
  if (this.dayServiceSubscription) {
    this.dayServiceSubscription.unsubscribe();
  }
}  

模板

<div class="tr">
  <div class="align-left">Phishing & Other Frauds</div>
  <div class="align-right">{{ webProtectionHTML?.phishing}}</div>
</div>
<div class="tr">
  <div class="align-left">Spam URLs</div>
  <div class="align-right">{{ webProtectionHTML?.spamURLs}}</div>
</div>
<div class="tr">
  <div class="align-left">Malware Sites</div>
  <div class="align-right">{{ webProtectionHTML?.malware}}</div>
</div>
于 2020-04-04T22:13:55.893 回答
1

toPromise对于 API,您可以使用方法作为承诺返回。它将convert订阅承诺。所以即使你使用 async 3 times。它将解决 promise once

this.webProtectionHTML$ = this.dayService$
      .pipe(
        mergeMap((days: DaysPeriod // params added to request url) => this.httpClient.get(`api/data_processor_classlib.php....`//request url, { responseType: 'text' })),
        map((html: string) => {
    //get html code and find data return as json data
    let result = this.getWebProtectionData(html)
          return result
        })).toPromise()
于 2020-04-04T22:23:19.983 回答
1

它发出了 3 个请求,因为代码this.webProtectionHTML$在模板中指定了 3 个异步管道。

你有两个解决方案:

1.在模板中使用单个异步管道

在条件中使用和设置带有别名语句的ngIf异步管道as

<div *ngIf="webProtectionHTML$ | async as webProtection"> // define async here
    <div class="tr">
      <div class="align-left">Phishing & Other Frauds</div>
      <div class="align-right">{{ webProtection.phishing}}</div>
    </div>
    <div class="tr">
      <div class="align-left">Spam URLs</div>
      <div class="align-right">{{ webProtection.spamURLs}}</div>
    </div>
    <div class="tr">
      <div class="align-left">Malware Sites</div>
      <div class="align-right">{{ webProtection.malware}}</div>
    </div>
</div>

2. 使用shareReplay() rxjs 算子 该算子用于将之前的数据重播给所有订阅者。shareReplay(1)表示重放最后的数据。

this.webProtectionHTML$ = this.dayService$
      .pipe(
        mergeMap((days: DaysPeriod // params added to request url) => this.httpClient.get(`api/data_processor_classlib.php....`//request url, { responseType: 'text' })),
        map((html: string) => {
          //get html code and find data return as json data
          let result = this.getWebProtectionData(html)
          return result
        })),
        shareReplay(1)

两者都有效,但如果我可以选择这种情况,我喜欢第一个解决方案。

希望能帮助到你

于 2020-04-05T12:25:52.793 回答
0

因为您使用 调用它 3 次(webProtectionHTML$|async),所以每次调用它并显示不同成员的值。如果您只调用一次this.webProtectionHTML$constructoror中调用它ngOnInit并将返回的值分配给一个局部变量,您可以使用它的值来绑定它。

于 2020-04-04T22:15:35.473 回答