RxJS withDelayedLoader操作符问题:forkJoin重复调用求解
RxJS withDelayedLoader操作符问题:forkJoin被调用两次的原因与解决
需求说明
我实现了一个RxJS操作符withDelayedLoader,规则如下:
- 若请求耗时≤500ms,不显示加载状态
- 若请求耗时>500ms,则加载状态至少显示800ms,直到请求完成
问题描述
当前操作符内部的forkJoin被调用了两次,需要分析原因并给出修复方案。
原始代码示例
const withDelayedLoader = ( showDelay = 500, interval = 800 ) => { return function (source$) { return source$.pipe( switchMap((pending) => { const show$ = timer(showDelay).pipe(filter(() => pending),map(() => pending)) const hide$ = forkJoin([source$, timer(interval)]).pipe(map(() => false)); return merge(show$, hide$); }), ); }; }; // 模拟请求状态流 const request$ = interval(2000).pipe( startWith(-1), take(2), map((i) => !(i === 0)) ) request$.pipe(withDelayedLoader())
原因分析
- 重复订阅原始流:在
switchMap内部的hide$中,你用forkJoin([source$, timer(interval)])重新订阅了一次原始的source$(即request$)。而request$里的startWith(-1)会在每次新订阅时重新发射初始值,这就触发了第二次switchMap执行,导致forkJoin被调用两次。 - 数据流触发逻辑:
request$的初始发射值-1会被转换为true,进入switchMap后,hide$的订阅又会让request$再发射一次-1,进而触发第二次switchMap。
解决建议
核心是避免重复订阅原始流,改用当前switchMap接收到的状态关联完成信号,同时确保加载时长符合要求。修改后的代码如下:
修复后的withDelayedLoader操作符
const withDelayedLoader = ( showDelay = 500, minShowDuration = 800 ) => { return function (source$) { return source$.pipe( switchMap((pending) => { // 非加载状态直接返回隐藏信号 if (!pending) { return of(false); } // 延迟500ms后发出显示加载的信号 const show$ = timer(showDelay).pipe(map(() => true)); // 等待加载完成信号(source$发出false),同时确保至少显示800ms const hide$ = combineLatest([ source$.pipe(filter(val => !val)), // 监听加载完成的信号 timer(minShowDuration) ]).pipe(map(() => false)); // 只取显示、隐藏两个信号,避免多余发射 return merge(show$, hide$).pipe(take(2)); }) ); }; };
调整请求模拟流(更贴合真实场景)
// 模拟真实请求:先发出true(开始加载),2秒后发出false(加载完成) const request$ = concat( of(true), timer(2000).pipe(map(() => false)) );
修改说明
- 去掉了对原始流的重复订阅,改用
filter(val => !val)监听加载完成的信号 - 用
combineLatest替代forkJoin,确保同时满足「请求完成」和「至少显示800ms」两个条件 - 增加了非加载状态的快速返回逻辑,优化性能
使用示例
const loading$ = request$.pipe(withDelayedLoader());
<div class="loader" *ngIf="loading$ | async">Loading...</div>
内容的提问来源于stack exchange,提问作者Ievgen Chernetsov
相关产品推荐
相关产品推荐

