关于RxJS中两次使用switchMap时异常日志输出的疑问
RxJS连续switchMap导致意外输出的问题与解决
我写了两段RxJS代码:
- 按钮A的点击流仅使用一次switchMap,控制台输出符合预期;
- 按钮AB的点击流连续使用两次switchMap,多次点击时控制台出现预期外的「B 0」日志。
根据RxJS官方对switchMap的描述:
When a new inner Observable is emitted, switchMap stops emitting items from the earlier-emitted inner Observable and begins emitting items from the new one. It continues to behave like this for subsequent inner Observables.
我产生困惑:为何在第二个switchMap前触发新的Observable时,会出现B的日志?希望消除该输出。
代码示例
import { fromEvent, switchMap, interval, take, map, tap } from 'rxjs'; // 按钮A:单次switchMap const clicksA = fromEvent(document.getElementById('A'), 'click'); const resultA = clicksA.pipe( tap(()=>console.log('------ START [Button A] ------')), switchMap(() => interval(500).pipe(take(2),map((i)=>`A ${i}`))), tap((A)=>console.log(A)) ); resultA.subscribe(); // 按钮AB:连续两次switchMap const clicksAB = fromEvent(document.getElementById('AB'), 'click'); const resultAB = clicksAB.pipe( tap(()=>console.log('------ START [Button A+B] ------')), switchMap(() => interval(500).pipe(take(2),map((i)=>`A ${i}`))), tap((A)=>console.log(A)), switchMap(() => interval(500).pipe(take(2),map((i)=>`B ${i}`))), tap((B)=>console.log(B)) ); resultAB.subscribe();
控制台输出示例
单次switchMap(按钮A)输出:
------ START [Button A] ------ ------ START [Button A] ------ A 0 ------ START [Button A] ------ A 0 A 1
连续两次switchMap(按钮AB)输出:
------ START [Button A+B] ------ ------ START [Button A+B] ------ A 0 ------ START [Button A+B] ------ B 0 <----- 意外输出 A 0 A 1 B 0 B 1
原因分析
问题出在连续switchMap的独立作用域:
- 第一个switchMap仅负责取消自己内部的
intervalObservable(即A的流)。当新的点击事件触发时,它会终止当前正在执行的A流,但如果之前的A流已经发出了A 0,这个值已经流入了第二个switchMap。 - 第二个switchMap会基于
A 0创建新的B流,这个B流不受第一个switchMap的控制——即使第一个switchMap已经切换到新的A流,已经启动的B流会继续执行直到完成,所以会出现意外的B 0。
简单说:switchMap只管理自己创建的内部Observable,上游的switchMap切换不会影响下游已经启动的内部流。
解决方案
将两个switchMap合并为一个,让整个A+B的流成为第一个switchMap的内部Observable,这样新点击触发时,switchMap会终止整个A+B的流,包括已经启动的B流。
修改后的按钮AB代码:
const resultAB = clicksAB.pipe( tap(()=>console.log('------ START [Button A+B] ------')), switchMap(() => // 将A和B的流合并为一个内部Observable interval(500).pipe( take(2), map(i => `A ${i}`), tap(A => console.log(A)), // 等A流完成后再启动B流 concatMap(() => interval(500).pipe( take(2), map(i => `B ${i}`), tap(B => console.log(B)) ) ) ) ) );
这样修改后,多次点击按钮AB时,不会再出现意外的B 0输出,因为新点击会直接终止整个A+B的内部流,包括未完成的B流。
内容的提问来源于stack exchange,提问作者housuyuan
相关产品推荐
相关产品推荐

