网站搜索

typescript - RXJS 的 takeUntil 和 switchMap 与定时器一起使用似乎无法完成内部定时器


我有以下代码:

  private takeControlTaskRunning$ = new BehaviorSubject<boolean>(false);

  private initTakeControlTask() {
    this.takeControlTaskRunning$
      .pipe(
        takeUntil(this.dependencies.destroyed$),
        switchMap((isRunning) => {
          if (!isRunning) {
            return of(false);
          }
          return timer(0, this.updatePeriodMilliseconds).pipe(
            takeUntil(this.dependencies.destroyed$), <--- Why is this needed?
            map(() => {
              return true;
            })
          );
        }),
        filter((isRunning) => isRunning === true),
      )
      .subscribe(async () => {
        await this.syncVideo();
      });
  }

  public takeControl() {
    this.takeControlTaskRunning$.next(true);
  }

  public stopControl() {
    this.takeControlTaskRunning$.next(false);
  }


当我在 fakeAsync 测试中运行此时,

  describe('takeControl', () => {
    it('runs the update task periodically', fakeAsync(async () => {
      // Given an controller instance
      const { controller, dependencies } = await create();

      // When takeControl is called
      await controller.takeControl();
      // And some time passes
      jasmine.clock().tick(1);

      // Then the update task should be running
      expect(
        dependencies.updateSync as jasmine.Spy
      ).toHaveBeenCalle();

      destroyed$.next();
      flush();
    }));
  });

我收到以下错误:

Error: 1 periodic timer(s) still in the queue. 

添加一个内部的 takeUntil 似乎可以解决问题,但我不明白为什么这是必要的。就我所理解,外部的 takeUntil 应该会完成 observable,而 switchMap 应该会完成内部的 timer,为什么情况不是这样呢?

答案#1

问题在于运算符的顺序。一般原则是,takeUntil() 应该总是在链中的最后一个运算符。

在你的情况下发生的是,this.takeControlTaskRunning$ 发出值,该值通过 switchMap 映射到 timer()。但是当 takeUntil() 发出 complete 通知时,它会被 switchMap 停止,因为 switchMap 仍在等待其内部 Observable(在这种情况下是 timer())完成。

如果你在 switchMap() 之后使用 takeUntil(),complete 通知将会到达观察者,这会触发取消订阅,即使 switchMap() 中的内部 Observable 仍在等待,也会立即被取消订阅。