You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Async Pipe时避免后端Observable重复请求的解决方案

解决Angular模板中getFoos方法导致后端重复请求的问题

问题根源

  1. Angular的变更检测机制会频繁调用模板中的getFoos方法,每次调用都会生成新的Observable实例,引发重复订阅。
  2. 当前逻辑中,获取到新Foo后调用obsFoos.next会触发Foos$推送新值,进而让所有订阅getFoos返回Observable的地方重新执行逻辑,形成请求循环。
  3. 缺少对已请求ID的缓存,导致同一someId被多次触发请求。

解决方案

通过三层缓存+优化变更检测策略,彻底解决重复请求问题:

1. 添加缓存逻辑

在组件中新增三个缓存容器,分别存储已加载的Foo、正在进行的请求、以及每个(rowIndex, matSelectIndex)对应的Observable:

import { Component, ChangeDetectionStrategy } from '@angular/core';
import { BehaviorSubject, Observable, of, iif, switchMap, tap, map, shareReplay, catchError } from 'rxjs';
import { Foo, KsFoosService } from './your-services-path';

@Component({
  selector: 'your-component',
  templateUrl: './your-component.html',
  // 开启OnPush变更检测,减少不必要的方法调用
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class YourComponent {
  private obsFoos: BehaviorSubject<Foo[]> = new BehaviorSubject<Foo[]>([]);
  public Foos$: Observable<Foo[]> = this.obsFoos.asObservable();
  public otherFoos$: Observable<Foo[]> = of([]); // 替换成你的otherFoos$逻辑

  // 缓存已加载的Foo,key为someId
  private fooCache = new Map<string, Foo>();
  // 缓存正在进行的请求,防止同一ID并发请求
  private pendingRequests = new Map<string, Observable<Foo>>();
  // 缓存每个(rowIndex, matSelectIndex)对应的Observable,避免重复创建
  private fooObservables = new Map<string, Observable<Foo[]>>();

  constructor(private _ksFoosService: KsFoosService) {}

  private getId(rowIndex: number, selectionIndex: number): string {
    // 替换成你的getId逻辑
    return `${rowIndex}-${selectionIndex}`;
  }

  public getFoos(rowIndex: number, selectionIndex: number): Observable<Foo[]> {
    const key = `${rowIndex}-${selectionIndex}`;
    
    // 1. 若已有缓存的Observable,直接返回
    if (this.fooObservables.has(key)) {
      return this.fooObservables.get(key)!;
    }

    const someId = this.getId(rowIndex, selectionIndex);
    if (!someId) {
      const obs = this.otherFoos$;
      this.fooObservables.set(key, obs);
      return obs;
    }

    // 2. 若已加载过该someId的Foo,直接返回缓存值
    if (this.fooCache.has(someId)) {
      const currentFoos = this.obsFoos.value;
      const obs = of([...currentFoos, this.fooCache.get(someId)!]);
      this.fooObservables.set(key, obs);
      return obs;
    }

    // 3. 若有正在进行的请求,复用该请求
    if (this.pendingRequests.has(someId)) {
      const obs = this.pendingRequests.get(someId)!.pipe(
        map(res => [...this.obsFoos.value, res])
      );
      this.fooObservables.set(key, obs);
      return obs;
    }

    // 4. 发起新请求并缓存结果
    const request$ = this._ksFoosService.getById(someId).pipe(
      tap(res => {
        this.fooCache.set(someId, res);
        const currentFoos = this.obsFoos.value;
        // 仅当Foo不在当前数组时更新全局列表
        if (!currentFoos.some(x => x.id === someId)) {
          this.obsFoos.next([...currentFoos, res]);
        }
        this.pendingRequests.delete(someId);
      }),
      map(res => [...this.obsFoos.value, res]),
      catchError(() => {
        this.pendingRequests.delete(someId);
        return this.otherFoos$;
      }),
      shareReplay(1) // 缓存请求结果,后续订阅直接取缓存
    );

    this.pendingRequests.set(someId, request$);
    this.fooObservables.set(key, request$);
    return request$;
  }
}

2. 模板保持原结构

无需修改模板代码,因为缓存逻辑已经确保每个(rowIndex, matSelectIndex)组合只会生成一次Observable,且请求结果被复用:

<mat-option *ngFor="let foo of getFoos(rowIndex, matSelectIndex) | async"
            [ngClass]="{'inactiveFoo': !foo?.status}"
            [value]="foo.id">
  <span [innerText]="foo.name"></span>
</mat-option>

关键优化点说明

  • fooCache:存储已加载的Foo,避免同一ID重复发起请求。
  • pendingRequests:处理并发场景,防止同一ID同时发起多个请求。
  • fooObservables:缓存每个行/选择框组合的Observable,避免变更检测时重复创建实例。
  • shareReplay(1):缓存请求结果,后续订阅直接获取缓存值,不重新发起请求。
  • ChangeDetectionStrategy.OnPush:减少不必要的变更检测触发,降低getFoos方法的调用频率。

内容的提问来源于stack exchange,提问作者Ahmed Aljazara

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 15:35:13