Rxjs-意外发布重播+;refCount变为0后的refCount行为

Rxjs-意外发布重播+;refCount变为0后的refCount行为,rxjs,rxjs5,rxjs6,Rxjs,Rxjs5,Rxjs6,我的用例是这样的:我使用websocket连接到一个服务,并从该服务获取定期(但不可预测)的健康数据。该应用程序可能有多个此数据流的用户,因此我想共享它。新订阅者应该看到最近发出的健康数据。我还想在没有更多订户时关闭websocket 我的应用程序使用了shareReplay(1)很长一段时间,直到发现它泄漏了底层连接()。此时,我们更改为管道(publishReplay(1),refCount)。事实证明,这也有一个微妙的,我没有预料到的: 订户A连接&websocket连接已建立 订户B正确

我的用例是这样的:我使用websocket连接到一个服务,并从该服务获取定期(但不可预测)的健康数据。该应用程序可能有多个此数据流的用户,因此我想共享它。新订阅者应该看到最近发出的健康数据。我还想在没有更多订户时关闭websocket

我的应用程序使用了
shareReplay(1)
很长一段时间,直到发现它泄漏了底层连接()。此时,我们更改为
管道(publishReplay(1),refCount)
。事实证明,这也有一个微妙的,我没有预料到的:

  • 订户A连接&websocket连接已建立
  • 订户B正确连接和共享,并获取最新数据
  • A和B都断开。网匣被拆掉了
  • 订户C已连接,但只需要一个值
    take(1)
    。返回由
    publishReplay(1)
    缓存的值 在步骤4中,我真的希望重新创建websocket。缓存的值没有任何用处。
    publishReplay
    的timewindow参数很诱人,但也不是我想要的

    我已经通过使用
    管道(multicast(()=>new ReplaySubject(1)),refCount())
    找到了一个解决方案,但我对Rx的了解还不足以理解这一点的全部含义

    我的问题是——实现我想要的行为的最佳方式是什么

    谢谢

    代码示例可以在 内联代码

    const { Observable, ReplaySubject } = require('rxjs');
    const { tap, multicast, take, publishReplay, refCount } = require('rxjs/operators');
    
    const log = console.log;
    
    function eq(a, b) {
      let result = JSON.stringify(a) == JSON.stringify(b);
      if (!result) {
        log('eq failed', a, b);
      }
      return result;
    }
    
    function assert(cond, msg) {
      if (!cond) {
        log('****************************************');
        log('Assert failed: ', msg);
        log('****************************************');
      }
    }
    
    function delay(t) {
      return new Promise(resolve => {
        setTimeout(resolve, t);
      });
    }
    
    let liveCount = 0;
    
    // emitValue 1 happens at 100ms, 2 at 200ms etc
    function testSource() {
      return Observable.create(function(observer) {
        let emitValue = 1;
        liveCount++;
        log('create');
        let interv = setInterval(() => {
          log('next --------> ', emitValue);
          observer.next(emitValue);
          emitValue++;
        }, 100);
    
        return () => {
          liveCount--;
          log('destroy');
          clearInterval(interv);
        };
      });
    }
    
    async function doTest(name, o) {
      log('\nDOTEST: ', name);
      assert(liveCount === 0, 'Start off not live');
      let a_vals = [];
      o.pipe(take(4)).subscribe(x => {
        a_vals.push(x);
      });
      await delay(250);
      assert(liveCount === 1, 'Must be alive');
    
      let b_vals = [];
      o.pipe(take(2)).subscribe(x => {
        b_vals.push(x);
      });
      assert(liveCount === 1, 'Two subscribers, one source');
      await delay(500);
      assert(liveCount === 0, 'source is destroyed');
      assert(eq(a_vals, [1, 2, 3, 4]), 'a vals match');
      assert(eq(b_vals, [2, 3]), 'b vals match');
    
      let c_vals = [];
      o.pipe(take(2)).subscribe(x => {
        c_vals.push(x);
      });
      assert(liveCount === 1, 'Must be alive');
    
      await delay(300);
      assert(liveCount === 0, 'Destroyed');
      assert(eq(c_vals, [1, 2]), 'c_vals match');
    }
    
    async function main() {
      await doTest(
        'bad: cached value is stale',
        testSource().pipe(
          publishReplay(1),
          refCount()
        )
      );
      await doTest(
        'good: But why is this different to publish replay?',
        testSource().pipe(
          multicast(() => new ReplaySubject(1)),
          refCount()
        )
      );
      await doTest(
        'bad: But why is this different to the above?',
        testSource().pipe(
          multicast(new ReplaySubject(1)),
          refCount()
        )
      );
    }
    main();
    

    重新表述cartant的评论:

    publishReplay
    将在引擎盖下使用单个ReplaySubject,该主题取消订阅,然后由
    refCount
    重新订阅。因此,它的缓存值将被重放。当您将
    多播
    与工厂一起使用时,每次
    refCount
    un-然后重新订阅-都会创建一个新的ReplaySubject,因此没有缓存值

    以下是cartant的链接,因为评论中的链接无法访问:

    从文章中:

    多播基础设施的主题可以重新订阅


    publishReplay
    在引擎盖下使用
    multicast
    ,不提供工厂,但重复使用相同的ReplaySubject。

    管道(multicast(()=>new ReplaySubject(1)),refCount())
    ,您也可以阅读解释。谢谢@cartant。我以前读过这些文章,但仍然没有真正“理解”。究竟是什么让
    多播(…)
    解决方案起作用?我想我的心理模型在某个地方与现实不符。在我看来,这种生命周期管理隐藏了Rx 90%的概念复杂性。当工厂被传递到
    多播
    时,现有主题将从源取消订阅,并在ref计数为零时丢弃。然后,如果以后再订阅,将创建一个新主题并订阅源。
    发布
    变体不会将工厂传递给
    多播
    ——这就是它们与
    共享
    变体的不同之处。