发出其他可观察值后过滤RxJS流,直到计时器用尽

时间:2020-10-04 11:10:24

标签: javascript angular rxjs

我想在RxJS中实现以下行为,但是找不到使用可用运算符的方法:

  • 流A :由连续的事件流(例如浏览器滚动)生成
  • 流B :由另一个任意事件(例如某种用户输入)生成
  • B 发出值时,我想暂停 A 的处理,直到经过指定的时间。 A在此时间范围内发出的所有值都将被丢弃。
  • B 在此时间间隔内发出另一个值时,该时间间隔将重置。
  • 经过时间间隔后,不再过滤发出的 A 值。
// Example usage.
streamA$
  .pipe(
    unknownOperator(streamB$, 800),
    tap(val => doSomething(val))
  )
// Output: E.g. [event1, event2, <skips processing because streamB$ emitted>, event10, ...]

// Operator API.
const unknownOperator = (pauseProcessingWhenEmits: Observable<any>, pauseIntervalInMs: number) => ...

我认为throttle可以用于此用例,但是直到 B 首次发射(它永远不会!)之前,它不会让任何发射通过。

streamA$
  .pipe(
    // If B does not emit, this never lets any emission of A pass through!
    throttle(() => streamB$.pipe(delay(800)), {leading: false}),
    tap(val => doSomething(val))
  )

一个简单的技巧是手动订阅B,在Angular组件中发射值时存储时间戳,然后过滤直到经过指定的时间:
(显然与避免反应框架的副作用相反)

streamB$
  .pipe(
    tap(() => this.timestamp = Date.now())
  ).subscribe()

streamA$
  .pipe(
    filter(() => Date.now() - this.timestamp > 800),
    tap(val => doSomething(val))
  )

在构建自己的自定义运算符之前,我想咨询一下这里的专家是否有人知道该运算符(组合)没有引入副作用:)

1 个答案:

答案 0 :(得分:2)

我认为这是一种方法:

bModified$ = b$.pipe(
  switchMap(
    () => of(null).pipe(
      delay(ms),
      switchMapTo(subject),
      ignoreElements(),
      startWith(null).
    )
  )
)

a$.pipe(
  multicast(
    new Subject(),
    subject => merge(
      subject.pipe(
        takeUntil(bModified$)
      ),
      NEVER,
    )
  ),
  refCount(),
)

似乎这不是一个问题,解决方案必然涉及多播,但是在上述方法中,我使用了一种本地多播

这不是预期的多播行为,因为如果您多次订阅a$(比如说​​N次),则将到达N次,因此多播在该级别上不会发生

因此,让我们检查每个相关部分:

multicast(
  new Subject(),
  subject => merge(
    subject.pipe(
      takeUntil(bModified$)
    ),
    NEVER,
  )
),

第一个参数将指示用于实现该本地多播的主题类型。第二个参数是一个函数,更准确地称为选择器。它的单个参数是之前指定的参数(Subject实例)。每次订阅a$时都会调用此选择器功能。

我们从source code中可以看到:

selector(subject).subscribe(subscriber).add(source.subscribe(subject));

使用source.subscribe(subject)订阅了源。通过selector(subject).subscribe(subscriber)实现的是一个新的subscriber,它将成为Subject的观察者列表的一部分(它始终是相同的Subject实例),因为merge内部订阅提供的可观察对象。

我们使用merge(..., NEVER)是因为,如果订阅选择器的订户完成了,那么下次a$流再次变为活动状态时,将必须重新订阅源。通过附加NEVER,调用select(subject)的可观察结果表单将永远不会完成,因为要完成merge,它的所有可观察项都必须完成。

subscribe(subscriber).add(source.subscribe(subject))subscribedSubject之间建立连接,以便subscriber完成时,Subject实例将具有其unsubscribe method叫。

因此,假设我们已订阅a$a$.pipe(...).subscribe(mySubscriber)。正在使用的Subject实例将有一个订阅者,并且如果a$发出某些东西,mySubscriber将(通过主题)收到它。

现在让我们讨论bModified$发出时的情况

bModified$ = b$.pipe(
  switchMap(
    () => of(null).pipe(
      delay(ms),
      switchMapTo(subject),
      ignoreElements(),
      startWith(null).
    )
  )
)

首先,我们使用switchMap是因为一个要求是当b$发出时,计时器应该复位。但是,我认为这个问题的方式是,b$发出 2件事

  • 启动计时器(1)
  • 暂停a$的排放量(2)

(1)通过在takeUntil的订户中使用Subject来实现。通过使用startWithb$将立即发射,因此a$的发射将被忽略。在switchMap内部可观察的范围内,我们使用delay(ms)指定计时器应花费的时间。经过它之后,switchMapTo(subject)现在将在Subject的帮助下获得新的订户,这意味着a$将收到mySubscriber的排放量(而不必重新订阅源)。最后,使用ignoreElements是因为否则将在a$发出时表示b$也发出,这将导致a$再次停止。 switchMapTo(subject)之后是a$的通知。

基本上,我们可以通过以下方式实现可暂停的行为:当Subject实例作为一个订户(最多有 个)此解决方案),不暂停。如果没有,则表示已已暂停

编辑:或者,您可以查看pause operator中的rxjs-etc