调用next后订阅延迟Observable出现意外输出的RxJS问题
问题复现
以下是触发问题的代码:
let isNull = true; const a$ = new Subject<void>(); const b$ = a$.pipe( switchMap(() => { if (!isNull) return of(true).pipe( tap(() => console.log('should be shared')), delay(100) // (3) ); else return of(null); }), shareReplay(1) ); b$.subscribe((b) => console.log('1', b)); a$.next(); // (1) b$.subscribe((b) => console.log('2', b)); isNull = false; a$.next(); // (2) b$.subscribe((b) => console.log('3', b));
实际输出:
1 null 2 null should be shared 3 null // 不符合预期的行 1 true 2 true 3 true
预期输出:
1 null 2 null should be shared 1 true 2 true 3 true
原因分析
时序与缓存机制冲突
- 执行
a$.next()(标记1)时,switchMap返回同步的of(null),shareReplay(1)缓存null,第一个订阅者收到1 null;第二次订阅时直接读取缓存,输出2 null。 - 修改
isNull后执行a$.next()(标记2),switchMap切换到异步的of(true).pipe(delay(100)),但此时该Observable尚未推送任何值,shareReplay(1)的缓存仍为null。 - 第三次订阅时,
shareReplay会先返回缓存的null,导致输出3 null;100ms后delay完成,true被推送,所有订阅者收到对应true值。
移除delay后问题消失的原因
无delay时,of(true)同步推送值,a$.next()(标记2)执行后shareReplay缓存立即更新为true,第三次订阅直接读取新缓存,不会输出旧的null。
优雅解决方案
要同时保留2 null的输出并避免3 null,需让新订阅者仅接收当前活跃Observable的值,结合switchMap与switchLatest实现:
let isNull = true; const a$ = new Subject<void>(); const b$ = a$.pipe( switchMap(() => { const source = !isNull ? of(true).pipe(tap(() => console.log('should be shared')), delay(100)) : of(null); // 为每个切换后的源单独配置shareReplay,确保缓存独立 return source.pipe(shareReplay(1)); }), // 始终订阅最新的Observable,新订阅者不会读取旧源的缓存 switchLatest() ); b$.subscribe((b) => console.log('1', b)); a$.next(); b$.subscribe((b) => console.log('2', b)); isNull = false; a$.next(); b$.subscribe((b) => console.log('3', b));
输出结果:
1 null 2 null should be shared 1 true 2 true 3 true
方案解析
- 每个
switchMap返回的Observable单独使用shareReplay(1),确保缓存仅属于当前活跃的数据源,避免跨源缓存污染。 switchLatest操作符会自动切换到最新的Observable,新订阅者只会等待当前活跃源的推送,不会读取旧源的缓存值,完美匹配预期输出。
内容的提问来源于stack exchange,提问作者Szymon
相关产品推荐
相关产品推荐

