Angular API调用每秒重复触发问题排查求助
API持续重复调用问题排查与修复
问题背景
将Angular静态数据表格改为通过API获取数据后,出现API每秒重复调用、数据不断刷新的问题,相关代码如下:
executions.service.ts(API调用逻辑)
import { Injectable, PipeTransform } from '@angular/core'; import { DecimalPipe } from '@angular/common'; import { BehaviorSubject, Observable, of, Subject } from 'rxjs'; import { debounceTime, delay, map, switchMap, tap } from 'rxjs/operators'; import { HttpClient } from '@angular/common/http'; interface SearchResult { jobs: any[]; total: number; } interface State { page: number; pageSize: number; searchTerm: string; startIndex: number; endIndex: number; totalRecords: number; } const compare = (v1: string | number, v2: string | number) => v1 < v2 ? -1 : v1 > v2 ? 1 : 0; function matches(job: any, term: string, pipe: PipeTransform) { return true; } @Injectable({ providedIn: 'root' }) export class AdvancedService { fetchedJobs: any[] = []; fetchedJobs$: Observable<any[]>; private _loading$ = new BehaviorSubject<boolean>(true); private _search$ = new Subject<void>(); private _jobs$ = new BehaviorSubject<any[]>([]); private _total$ = new BehaviorSubject<number>(0); private _state: State = { page: 1, pageSize: 10, searchTerm: '', startIndex: 0, endIndex: 9, totalRecords: 0 }; constructor(private pipe: DecimalPipe, private http: HttpClient) { this.fetchedJobs$ = this.http.get<any[]>(`https://api.publicapis.org/entries`).pipe( tap((res: any[])=>{ this.fetchedJobs=res; })); this._search$.pipe( tap(() => this._loading$.next(true)), debounceTime(200), switchMap(() => this._search()), delay(200), tap(() => this._loading$.next(false)) ).subscribe(result => { this._jobs$.next(result.jobs); this._total$.next(result.total); }); this._search$.next(); } get jobs$() { return this._jobs$.asObservable(); } get total$() { return this._total$.asObservable(); } get loading$() { return this._loading$.asObservable(); } get page() { return this._state.page; } get pageSize() { return this._state.pageSize; } get searchTerm() { return this._state.searchTerm; } get startIndex() { return this._state.startIndex; } get endIndex() { return this._state.endIndex; } get totalRecords() { return this._state.totalRecords; } set page(page: number) { this._set({ page }); } set pageSize(pageSize: number) { this._set({ pageSize }); } set startIndex(startIndex: number) { this._set({ startIndex }); } set endIndex(endIndex: number) { this._set({ endIndex }); } set totalRecords(totalRecords: number) { this._set({ totalRecords }); } set searchTerm(searchTerm: string) { this._set({ searchTerm }); } private _set(patch: Partial<State>) { Object.assign(this._state, patch); this._search$.next(); } private _search(): Observable<SearchResult> { const { pageSize, page, searchTerm } = this._state; return this.fetchedJobs$.pipe( map((res: any[]) => { let jobs = Object.values(res); jobs = jobs.filter(job => matches(job, searchTerm, this.pipe)); const total = jobs.length; this.totalRecords = jobs.length; this._state.startIndex = (page - 1) * this.pageSize + 1; this._state.endIndex = (page - 1) * this.pageSize + this.pageSize; if (this.endIndex > this.totalRecords) { this.endIndex = this.totalRecords; } jobs = jobs.slice(this._state.startIndex - 1, this._state.endIndex); return { jobs, total } as SearchResult; })); } }
app.component.ts(订阅逻辑)
import { Component, OnInit, ViewChildren, QueryList } from '@angular/core'; import { Observable } from 'rxjs'; import { AdvancedService } from './executions.service'; import { CommonModule, DecimalPipe } from '@angular/common'; @Component({ selector: 'app-component', standalone: true, templateUrl: './app.component.html', styleUrls: ['./app.component.scss'], imports: [CommonModule], providers: [AdvancedService, DecimalPipe] }) export class AppComponent implements OnInit { tables$: Observable<any[]>; total$: Observable<number>; testbooks: any[] = []; constructor(public service: AdvancedService) { this.tables$ = service.jobs$; this.total$ = service.total$; } ngOnInit(): void { this.tables$.subscribe((data: any) => { console.log(data.length); this.testbooks = data; }); } }
模板代码
<div class="row"> <div class="col-lg-12"> <div class="card"> <div class="card-body"> <div class="table-responsive"> <table id="datatable" class="table table-bordered dt-responsive nowrap w-100 datatables"> <tbody *ngFor="let job of testbooks;let i=index;"> <tr> <td> <div class="card"> <div class="card-body"> <h1>test</h1> </div> </div> </td> </tr> </tbody> </table> </div> </div> </div> </div> </div>
错误原因
- API请求未缓存:
fetchedJobs$是未缓存的HTTP Observable,每次_search()执行时都会重新订阅它,发起新的API请求。 - 状态更新触发循环:在
_search()中调用this.totalRecords = jobs.length和this.endIndex = ...时,会触发对应的setter方法,而setter内的_set()会调用this._search$.next(),重新触发搜索流程,形成无限循环。 - 接口数据解析错误:目标API返回的是包含
entries数组的对象,代码中使用Object.values(res)错误解析数据,导致后续处理异常。
修复方案
1. 缓存API请求结果
使用shareReplay(1)操作符缓存API响应,确保只发起一次请求:
// 在executions.service.ts中导入shareReplay import { shareReplay } from 'rxjs/operators'; // 修改constructor中的fetchedJobs$初始化 this.fetchedJobs$ = this.http.get<any>('https://api.publicapis.org/entries').pipe( tap((res: any) => { this.fetchedJobs = res.entries; // 正确获取接口返回的entries数组 }), shareReplay(1) // 缓存结果,避免重复请求 );
2. 避免状态更新触发循环
在_search()中直接修改_state对象,不调用setter方法,避免触发新的搜索:
private _search(): Observable<SearchResult> { const { pageSize, page, searchTerm } = this._state; return this.fetchedJobs$.pipe( map((res: any) => { let jobs = res.entries || []; // 过滤逻辑保持不变 jobs = jobs.filter(job => matches(job, searchTerm, this.pipe)); const total = jobs.length; // 直接计算并更新_state,不调用setter const totalRecords = jobs.length; const startIndex = (page - 1) * pageSize + 1; let endIndex = (page - 1) * pageSize + pageSize; if (endIndex > totalRecords) { endIndex = totalRecords; } // 直接修改_state,避免触发_search$.next() Object.assign(this._state, { totalRecords, startIndex, endIndex }); jobs = jobs.slice(startIndex - 1, endIndex); return { jobs, total } as SearchResult; }) ); }
3. 优化组件订阅(可选)
使用Angular的async管道自动管理订阅,避免手动订阅的内存泄漏:
// app.component.ts中移除testbooks和ngOnInit中的订阅 export class AppComponent { tables$: Observable<any[]>; total$: Observable<number>; constructor(public service: AdvancedService) { this.tables$ = service.jobs$; this.total$ = service.total$; } }
修改模板:
<tbody *ngFor="let job of tables$ | async; let i=index"> <tr> <td> <div class="card"> <div class="card-body"> <h1>{{ job.API }}</h1> <!-- 展示实际接口字段 --> </div> </div> </td> </tr> </tbody>
内容的提问来源于stack exchange,提问作者Rima
相关产品推荐
相关产品推荐

