Angular中如何在数据已加载时跳过HTTP请求?
基于ID的HTTP请求缓存优化方案
你的核心需求是按请求ID缓存数据、避免重复发起HTTP请求,同时解决重复订阅收到相同值的问题,下面是具体的优化方案:
1. 先澄清现有代码的作用
你当前的代码其实已经实现了同ID请求复用:每个ID对应一个缓存的Observable,shareReplay(1)会把请求结果缓存下来,新订阅者直接拿到缓存值,不会触发新的HTTP请求。你觉得shareReplay只适用于单个请求是误解——它是作用于每个Observable实例的,每个ID对应独立的Observable,不同ID的请求不会互相干扰。
至于两次订阅收到同一个值,这是shareReplay的预期行为:它会把之前发射的值重播给新订阅者。如果这不是你想要的,可以通过下面的方案调整。
2. 优化方案一:缓存数据而非Observable
直接缓存请求返回的具体数据,既能避免重复发请求,还能灵活控制是否重复触发回调:
export class AppComponent { // 缓存已加载完成的数据 private dataCache = new Map<number, any>(); // 缓存正在pending的请求,防止同一ID同时发起多个请求 private pendingRequests = new Map<number, Observable<any>>(); constructor(private http: HttpClient) {} ngOnInit() { this.load(1).subscribe((e) => { console.log('加载ID1:', e); }); setTimeout(() => { this.load(2).subscribe((e) => { console.log('加载ID2:', e); }); }, 3000); setTimeout(() => { this.load(1).subscribe((e) => { console.log('再次加载ID1:', e); }); }, 5000); } load(id: number): Observable<any> { // 优先返回已缓存的数据 if (this.dataCache.has(id)) { return of(this.dataCache.get(id)!); } // 如果请求正在进行,直接复用该请求 if (this.pendingRequests.has(id)) { return this.pendingRequests.get(id)!; } const request$ = this.http .get(`https://jsonplaceholder.typicode.com/todos/${id}`) .pipe( tap((data) => { // 请求成功后存入数据缓存 this.dataCache.set(id, data); // 移除pending标记 this.pendingRequests.delete(id); }), catchError((error) => { // 请求失败也清除pending标记 this.pendingRequests.delete(id); return throwError(() => error); }) ); this.pendingRequests.set(id, request$); return request$; } }
这种方式下,第二次订阅ID1时会直接返回缓存数据,不会发起新请求。如果不想重复处理数据,可以在订阅时加个判断,比如维护一个processedIds集合,跳过已处理的ID。
3. 优化方案二:保留Observable缓存,控制回调执行
如果还是想缓存Observable,只需要在订阅时判断是否已经处理过该数据即可:
export class AppComponent { private cache = new Map<number, Observable<any>>(); // 记录已处理过的ID private processedIds = new Set<number>(); constructor(private http: HttpClient) {} ngOnInit() { this.load(1).subscribe((e) => { if (!this.processedIds.has(1)) { console.log('处理ID1:', e); this.processedIds.add(1); } }); setTimeout(() => { this.load(2).subscribe((e) => { if (!this.processedIds.has(2)) { console.log('处理ID2:', e); this.processedIds.add(2); } }); }, 3000); setTimeout(() => { this.load(1).subscribe((e) => { if (!this.processedIds.has(1)) { console.log('处理ID1:', e); this.processedIds.add(1); } else { console.log('ID1已处理,跳过'); } }); }, 5000); } load(id: number): Observable<any> { if (this.cache.has(id)) { return this.cache.get(id)!; } const observable = this.http .get(`https://jsonplaceholder.typicode.com/todos/${id}`) .pipe(shareReplay(1)); this.cache.set(id, observable); return observable; } }
4. 可选:添加缓存过期逻辑
如果数据有实效性,可以给缓存加过期时间,比如5分钟后自动失效:
interface CacheItem { data: any; expiresAt: number; } private dataCache = new Map<number, CacheItem>(); private pendingRequests = new Map<number, Observable<any>>(); load(id: number): Observable<any> { const cachedItem = this.dataCache.get(id); // 检查缓存是否未过期 if (cachedItem && Date.now() < cachedItem.expiresAt) { return of(cachedItem.data); } // 后续请求逻辑同方案一,存入缓存时设置过期时间 const request$ = this.http.get(`https://jsonplaceholder.typicode.com/todos/${id}`) .pipe( tap((data) => { this.dataCache.set(id, { data, expiresAt: Date.now() + 5 * 60 * 1000 // 5分钟后过期 }); this.pendingRequests.delete(id); }) ); this.pendingRequests.set(id, request$); return request$; }
内容的提问来源于stack exchange,提问作者Amanda
相关产品推荐
相关产品推荐

