Angular中使用RxJS的SwitchMap未取消前序API请求问题
问题根因
switchMap无法取消旧请求的问题来自两个错误实现:
- 自定义的
createObservable方法未实现Observable取消逻辑:switchMap切换新Observable时会自动取消旧Observable的订阅,但你手动封装的fetch请求没有关联订阅取消的逻辑,哪怕订阅销毁,fetch请求仍然会正常执行完毕。 distinctUntilChanged操作符位置错误:当前放在switchMap之后是对接口返回结果去重,没有提前对输入的搜索关键词去重,相同关键词重复输入也会触发重复请求。
修复方案
方案1:给自定义fetch封装添加取消逻辑(适合必须用原生fetch的场景)
使用AbortController关联fetch请求的中断和Observable的取消回调,修复后的完整代码如下:
import {AfterViewInit, Component, ElementRef, OnInit, ViewChild} from '@angular/core'; import {fromEvent, Observable} from "rxjs"; import {debounceTime, distinctUntilChanged, map, switchMap} from "rxjs/operators"; @Component({ selector: "app-debounce-component", templateUrl: "./debounce-component.component.html", styleUrls: ['./debounce-component.component.css'] }) export class DebounceComponentComponent implements OnInit,AfterViewInit { constructor() { } @ViewChild('searchinput') ele:ElementRef ngOnInit(): void { } ngAfterViewInit(): void { fromEvent(this.ele.nativeElement , 'keyup').pipe( debounceTime(1000), map(evt=> evt['target']['value']), distinctUntilChanged(), // 提前对搜索关键词去重 switchMap(value=>{ console.log(value); return this.createObservable(value); }) ).subscribe(res => { // 处理返回结果 }) ; } createObservable(value):Observable<any>{ return Observable.create(observer=>{ // 初始化中断控制器 const controller = new AbortController(); const signal = controller.signal; fetch(`http://localhost:3000/courses/${value}`, {signal}) .then(res=> res.json()) .then(body=>{ observer.next(body); observer.complete(); }) .catch(err=>{ // 中断请求会抛出AbortError,可自行判断是否需要抛给订阅者 if (err.name !== 'AbortError') { observer.error(err); } }); // 订阅取消时执行的回调,中断fetch请求 return () => controller.abort(); }) } }
方案2:使用Angular自带的HttpClient(更推荐,原生支持取消)
Angular的HttpClient返回的Observable原生实现了取消逻辑,不需要手动封装中断,只需要两步调整即可:
- 先在AppModule中导入
HttpClientModule - 组件中注入HttpClient替换自定义fetch封装,修改后代码如下:
import {AfterViewInit, Component, ElementRef, OnInit, ViewChild} from '@angular/core'; import {fromEvent} from "rxjs"; import {debounceTime, distinctUntilChanged, map, switchMap} from "rxjs/operators"; import { HttpClient } from '@angular/common/http'; @Component({ selector: "app-debounce-component", templateUrl: "./debounce-component.component.html", styleUrls: ['./debounce-component.component.css'] }) export class DebounceComponentComponent implements OnInit,AfterViewInit { constructor(private http: HttpClient) { } @ViewChild('searchinput') ele:ElementRef ngOnInit(): void { } ngAfterViewInit(): void { fromEvent(this.ele.nativeElement , 'keyup').pipe( debounceTime(1000), map(evt=> evt['target']['value']), distinctUntilChanged(), switchMap(value=> this.http.get(`http://localhost:3000/courses/${value}`)) ).subscribe(res => { // 处理返回结果 }) ; } }
这种方式下switchMap切换新请求时,旧的HttpClient请求会自动中断,不需要额外处理取消逻辑。
内容的提问来源于stack exchange,提问作者Slow Death
相关产品推荐
相关产品推荐

