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

无法缓存多服务复用请求且无法取消重复点击请求的技术求助

解决方案:请求缓存与重复点击取消实现

核心思路

用一个专门的数据缓存服务(仅这一个服务使用RxJS),负责:

  • 管理每个接口的请求状态与缓存结果
  • 通过switchMap自动取消前一次pending请求
  • 对外暴露Promise风格的API,组件和其他服务无需接触RxJS

实现步骤

1. 实现数据缓存服务

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, Subject, switchMap, shareReplay, take } from 'rxjs';

@Injectable({ providedIn: 'root' })
export class DataCacheService {
  private pendingRequests = new Map<string, Observable<any>>();
  private cachedResponses = new Map<string, any>();
  private requestTrigger = new Subject<string>();

  constructor(private http: HttpClient) {
    // 统一处理请求触发流,switchMap自动取消前一次未完成请求
    this.requestTrigger.pipe(
      switchMap((key) => this.fetchAndCache(key))
    ).subscribe();
  }

  // 对外暴露的Promise方法,供组件/其他服务调用
  async fetchData(url: string): Promise<any> {
    // 优先返回缓存结果
    if (this.cachedResponses.has(url)) {
      return this.cachedResponses.get(url);
    }
    // 触发请求,返回Promise
    this.requestTrigger.next(url);
    return this.pendingRequests.get(url)?.pipe(take(1)).toPromise();
  }

  private fetchAndCache(url: string): Observable<any> {
    // 已有pending请求时直接复用
    if (this.pendingRequests.has(url)) {
      return this.pendingRequests.get(url)!;
    }
    // 发起新请求并缓存
    const request$ = this.http.get(url).pipe(
      shareReplay(1), // 缓存结果供后续订阅复用
      take(1),
      {
        next: (res) => {
          this.cachedResponses.set(url, res);
          this.pendingRequests.delete(url);
        },
        error: () => {
          this.pendingRequests.delete(url);
        }
      }
    );
    this.pendingRequests.set(url, request$);
    return request$;
  }
}

2. 业务服务组合多接口请求

以UserDetails为例,创建业务服务组合多个接口数据,对外返回Promise:

import { Injectable } from '@angular/core';
import { DataCacheService } from './data-cache.service';

@Injectable({ providedIn: 'root' })
export class UserService {
  constructor(private dataCache: DataCacheService) {}

  async getUserDetails(userId: string): Promise<{ user: any; signs: any }> {
    const [user, signs] = await Promise.all([
      this.dataCache.fetchData(`/api/users/${userId}`),
      this.dataCache.fetchData(`/api/users/${userId}/signs`)
    ]);
    return { user, signs };
  }
}

3. 组件调用(完全无RxJS)

组件直接调用业务服务的Promise方法,处理加载状态与结果:

import { Component } from '@angular/core';
import { UserService } from './services/user.service';

@Component({
  selector: 'app-root',
  template: `
    <button (click)="loadUser()" [disabled]="loading">加载用户数据</button>
    <div *ngIf="userDetails">
      <h3>{{ userDetails.user.name }}</h3>
      <p>签名:{{ userDetails.signs.content }}</p>
    </div>
  `
})
export class AppComponent {
  userDetails: any;
  loading = false;

  constructor(private userService: UserService) {}

  async loadUser() {
    this.loading = true;
    try {
      this.userDetails = await this.userService.getUserDetails('123');
    } catch (err) {
      console.error(err);
    } finally {
      this.loading = false;
    }
  }
}

关键特性说明

  • 重复请求拦截:同一接口存在pending请求或已缓存结果时,直接复用,避免重复调用后端
  • 重复点击取消:通过单requestTrigger流统一处理请求,switchMap自动取消前一次未完成请求
  • 低RxJS侵入:仅数据缓存服务使用RxJS,组件和业务服务完全基于Promise,符合项目限制
  • 数据共享:多个服务调用同一接口时,共享缓存结果,无需重复请求

解决你之前的问题

之前出现请求叠加,是因为每次点击都创建了新的Observable并订阅。现在通过单流统一处理所有请求触发,switchMap会自动终止前一次pending请求,不会产生叠加。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 03:28:23