You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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())

原因分析

  1. 重复订阅原始流:在switchMap内部的hide$中,你用forkJoin([source$, timer(interval)])重新订阅了一次原始的source$(即request$)。而request$里的startWith(-1)会在每次新订阅时重新发射初始值,这就触发了第二次switchMap执行,导致forkJoin被调用两次。
  2. 数据流触发逻辑: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 07:53:20