在switchMap中组合最大的动态数组是取消订阅并不断重新订阅

时间:2017-07-13 20:37:30

标签: rxjs

我至少有两个按钮,我想动态收听点击次数。 listeningArray$会发出一个我需要收听的按钮数组(ar)。当有人点击我正在收听的其中一个按钮时,我需要控制点击按钮的日志,并记录时间间隔内的值。

如果ar[1,2]转到[1],我们需要停止收听按钮#2上的点击次数。因此,需要删除2的DOM点击事件,这应触发.finally()运算符。但是对于1,我们应该继续订阅并且.finally()内的代码不应该运行,因为没有任何内容被取消订阅。

const obj$ = {};

Rx.Observable.combineLatest(
  Rx.Observable.interval(2000),
  listeningArray$ // Will randomly emit either [1] or [1,2]
)
.switchMap(([x, ar]) => {
    const observables = [];

    ar.forEach(n => {
        let nEl = document.getElementById('el'+n);

        obj$[n] = obj$[n] || Rx.Observable.fromEvent(nEl, 'click')
          .map(()=>{
            console.log(' el' + n);
          })
          .finally(() => {
            console.log(' FINALLY_' + n);
          });

        observables.push(obj$[n]);
    })

    return Rx.Observable.combineLatest(...observables);
})
.subscribe()

但是,每当间隔发出一个值时,发生的事情是,DOM事件ALL被删除,然后立即再次添加,并且.finally运算符内的代码运行1 2

这真让我很沮丧。我错过了什么?

这有点复杂,所以我创建了这个:https://jsfiddle.net/mfp22/xtca98vx/7/

2 个答案:

答案 0 :(得分:1)

FRP中的国家是不可改变的。因此,当您将map切换到第二个发射时,包含combineLatest的先前可观察[1,2]将被取消订阅并调用finally运算符。在订阅下一个仅包含[1]

之前

如果您只想取消订阅一个按钮,则可以在DOM中存储状态(将atr添加到按钮)并使用filter忽略按钮。 或者你可以在每个按钮上添加一个TakeWhile(),指示何时应该取消订阅,以便它可以调用它自己的finally()

答案 1 :(得分:1)

我其实非常接近,但我误解了switchMap的观点。

switchMap旨在取消订阅每次从上方发出新值时返回的observable。这就是为什么它可以用来取代旧的挂起Http请求,而不是需要新的请求。

我遇到的问题是可以预料到的。在订阅当前的观察之前,switchMap将取消订阅之前返回的观察者。正如我在问题中解释的那样,这是不可接受的。这是不可接受的原因是,在我的实际项目中,fromEvent观察者正在收听Firebase child_added事件,因此当这些冷观察者从没有订阅者变为拥有1个订阅者时,Firebase将随后解雇已经存在的每个孩子的事件,以及未来的孩子的事件。

我和mergeMap玩了一段时间,但是手动取消订阅以前返回的观察值非常困难和错误。

所以我为内部observable添加了一个订阅者,而switchMap正在执行unsubscribe from old => subscribe to new的进程,以便始终有一个订阅者。我使用takeUntil(Observable.timer(0))来确保订阅者没有建立并导致内存泄漏。

可能有更好的解决方案,但这是我找到的最好的解决方案。

const obj$ = {};

Rx.Observable.combineLatest(
  Rx.Observable.interval(2000),
  listeningArray$ // Will randomly emit either [1] or [1,2]
)
.switchMap(([x, ar]) => {
  const observables = [];

  ar.forEach(n => {
    let nEl = document.getElementById('el'+n);

    obj$[n] = obj$[n] || Rx.Observable.fromEvent(nEl, 'click')
      .map(()=>{
        console.log(' el' + n);
      })
      .finally(() => {
        console.log(' FINALLY_' + n);
      })
      .share();
    
    obj$[n].takeUntil(Rx.Observable.timer(0))
      .subscribe();
    observables.push(obj$[n]);
  })

  return Rx.Observable.combineLatest(...observables);
})
.subscribe()

我还必须添加.share()方法。无论如何我还是需要它。我正在使用这种模式让一些Angular组件声明他们需要什么数据,忽略其他组件可能需要的东西,以实现更好的关注点分离。因此,多个组件可以订阅相同的Firebase可观察对象,但.share()运算符可确保来自Firebase的每条消息仅处理一次(我将每个消息的调度操作分配给Redux存储)。

工作解决方案:https://jsfiddle.net/mfp22/xtca98vx/8/