RXJS-refCount变为0后出现意外的publishReplay + refCount行为

时间:2018-12-15 23:31:05

标签: rxjs rxjs5 rxjs6

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

我的应用使用了shareReplay(1)一段时间,直到发现它泄漏了基础连接(https://blog.strongbrew.io/share-replay-issue/)。此时,我们更改为pipe(publishReplay(1), refCount)。事实证明,这还具有我没想到的巧妙之处:

  1. 订户A连接并建立了websocket连接。
  2. 订户B正确连接和共享,以及获取最新数据。
  3. A和B都断开连接。 websocket被拆除
  4. 订户C连接,但只需要一个值take(1)。返回由publishReplay(1)缓存的值。

在步骤4中,我确实希望重新创建WebSocket。缓存的值没有用。 publishReplay的timewindow参数很诱人,但也不是我想要的。

我已经设法通过使用pipe(multicast(() => new ReplaySubject(1)), refCount())找到了解决方案,但是我对Rx的了解还不足以了解其全部含义。

我的问题是-实现我想要的行为的最佳方法是什么?

谢谢!

可以在https://repl.it/@bradb/MinorColdRouter看到代码示例 内联代码

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

1 个答案:

答案 0 :(得分:0)

改写来自 carant 的评论:

publishReplay 将在幕后使用单个 ReplaySubject,它会被取消订阅,然后由 refCount 重新订阅。因此它的缓存值被重放。当您将 multicast 与工厂一起使用时,每次 refCount 取消并重新订阅时都会创建一个新的 ReplaySubject - 因此,没有缓存值。

这里是 carant 的链接,因为评论中的链接无法访问:

来自文章:

<块引用>

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

publishReplay 在幕后使用 multicast 并且不提供工厂,而是重复使用相同的 ReplaySubject。