Как убрать лишние запросы в RX JS? - PullRequest
0 голосов
/ 05 апреля 2020

Я работаю над внешним проектом с Rx js и Angular Framework, и я хочу получить json данные из API "api / data_processor_classlib. php ....". Есть три части, на которые был подписан канал this.webProtectionHTML $ на HTML. Я не знаю, почему канал 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
        }))

Сетевой журнал:

enter image description here

Ответы [ 5 ]

3 голосов
/ 05 апреля 2020

Несколько предыдущих ответов правильно указывают на то, что каждое использование async канала приводит к запросу http - но не объясняет почему. На самом деле это не проблема с асин c трубой как таковой. Это потому, что ваш наблюдаемый http-запрос «холодный» (как объяснено, например, здесь: https://www.learnrxjs.io/learn-rxjs/concepts/rxjs-primer#what -is-an-observable ).

«Холодный» наблюдаемый означает, что он только начнет делать что-то и выдача значений, когда какой-то потребитель подписывается Более того, по умолчанию каждая новая подписка инициирует новое выполнение - даже если параллельно создаются несколько подписок на одну и ту же наблюдаемую. Это именно то, что вы заметили в своем коде: каждый асин c канал подписывается на наблюдаемое отдельно.

Есть несколько способов исправить это.

  1. Убедитесь, что у вас есть только одна подписка. Ответ @Michael D использует этот подход. Это довольно распространенный способ решения проблемы. Потенциальным недостатком является то, что подписка, созданная таким образом вручную, не отменяется автоматически после уничтожения компонента. В вашем примере это может не иметь большого значения (если dayService$ выдает только одно значение). Однако если компонент будет уничтожен до завершения http-запроса, этот http-запрос не будет отменен без написания дополнительного кода (включает реализацию метода жизненного цикла ngOnDestroy). Еще один недостаток - вам нужно будет вручную вызвать changeDetector.markForCheck(), если ваш компонент использует OnPu sh.

  2. Сделайте это наблюдаемым "горячим". «Горячий» означает, что действие asyn c уже началось, и все подписчики будут просто получать результат этого действия - независимо от того, сколько подписчиков там. Использование .toPromise (), как предлагает @xdeepakv, делает именно это. Обратите внимание, что обещания не подлежат отмене вообще - поэтому у вас не будет возможности отменить такой запрос. Еще один недостаток - он работает только в том случае, если ваша наблюдаемая выдает одно значение, а затем завершается (например, один HTTP-запрос).

  3. Вы можете использовать оператор shareReplay({refCount: true}), чтобы сделать свой наблюдаемый мультикаст - это позволяет нескольким подписчикам совместно использовать один и тот же результат. В этом случае вам не нужно будет менять шаблон (может иметь несколько асин c каналов) и получать выгоду от отмены автоматической отмены подписки / http, реализованной в канале asyn c.

this.webProtectionHTML$ = dayService$.pipe(
  mergeMap(...),
  map(...),
  shareReplay({refCount: true}) // <- making it a multicast
)
1 голос
/ 05 апреля 2020

Было сделано 3 запроса, потому что код указывает 3 асин c канала для this.webProtectionHTML$ в шаблоне.

У вас есть два решения:

1. Используйте одиночный асин c канал в шаблоне

Используйте ngIf и установите асин c канал с псевдонимом 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 () rx js operator Оператор используется для воспроизведения предыдущих данных для всех подписчиков. 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)

Оба работают, но если я могу выбрать для этого случая, мне нравится первое решение.

Надеюсь, это поможет

1 голос
/ 05 апреля 2020

Для API вы можете вернуться как обещание, используя метод toPromise. Это будет convert подписаться на обещание. Так что даже вы используете asyn c 3 times. Это разрешит обещание 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()
1 голос
/ 05 апреля 2020

Он вызывается три раза, потому что каждый 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>
0 голосов
/ 05 апреля 2020

Поскольку вы вызывали его 3 раза с помощью (webProtectionHTML$|async), каждый раз, когда он вызывал его и отображал значение другого члена. Если вы хотите, чтобы он вызывался только один раз, вызовите this.webProtectionHTML$ в constructor или ngOnInit и присвойте возвращаемое значение локальной переменной, которую вы можете использовать для привязки к ней.

...