为什么多次将相同的 observable 传递给 concat() 会这样?

Why passing same observable to concat() many times works that way?

我在文档中读到:

If you pass to concat the same Observable many times, its stream of values will be "replayed" on every subscription, which means you can repeat given Observable as many times as you like. If passing the same Observable to concat 1000 times becomes tedious, you can always use repeat.

https://rxjs.dev/api/index/function/concat

我的问题是:为什么它会那样工作?我希望它 return 仅来自第一个可观察值和之后的完整值(因为所有其他的也会完成,因为我们指的是同一个可观察的)。以这种方式工作与其他 concat() 行为是一致的。

concat的策略是订阅第一个提供的observable,完成后,concat订阅第二个observable,依此类推。

第二个方面是您提供冷可观察量。这意味着每次订阅完成时,observable 从头开始​​。您可以在此处找到有关冷热观测值的更多信息 What are the Hot and Cold observables?

这里是带有 marble diagrams 的测试游乐场,以便更好地理解。

const {concat} = rxjs;
const {take, mergeMap, tap} = rxjs.operators;
const {TestScheduler} = rxjs.testing;
const {expect} = chai;

const test = (testName, testFn) => {
  try {
    testFn();
    console.log(`Test PASS "${testName}"`);
  } catch (error) {
    console.error(`Test FAIL "${testName}"`, error.message);
  }
}

const createTestScheduler = () => new TestScheduler((actual, expected) => {
  expect(actual).deep.equal(expected);
});

test('should concat two cold observables', () => {
  const testScheduler = createTestScheduler();
  testScheduler.run((helpers) => {
    const { cold, hot, expectObservable } = helpers;
    const init$ = cold('       --e-------------------|');
    const letters$ = cold('      --a--b--c|           ');
    const numbers$ = cold('               --0--1--2|  ');
    const resultMarble = '     ----a--b--c--0--1--2--|';
    const result$ = init$.pipe(
      mergeMap(() => concat(letters$, numbers$))
    );
    expectObservable(result$).toBe(resultMarble);
  });
});

test('should conconcat two hot observables', () => {
  const testScheduler = createTestScheduler();
  testScheduler.run((helpers) => {
    const { cold, hot, expectObservable } = helpers;
    // the ^ character marks the zero time of hot observable
    const init$ = cold('       ----e---------------|');
    const letters$ = hot('-----^--a--b--c--d--------');
    const numbers$ = hot('---0-^----1--2--3--4------');
    const resultMarble = '     ------b--c-3--4-----|';
    const result$ = init$.pipe(
      mergeMap(() => concat(
        letters$.pipe(take(2)),
        numbers$.pipe(take(2))
      ))
    );
    expectObservable(result$).toBe(resultMarble);
  });
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/chai/4.1.2/chai.js"></script>
<script src="https://unpkg.com/rxjs@^7/dist/bundles/rxjs.umd.min.js"></script>

考虑以下因素:

// Defining a function
const addTwoNumbers = a => b => () => {
  console.log(`Adding ${a} + ${b}`);
  return a + b;
}

// Defining another function
const increment = addTwoNumbers(1);

// Defining another function
const incFive = increment(5);

// Invoking the final function to get a result
const result = incFive();

// Result is 6
console.log(`Result is ${result}`);


// Invoke 3 more times
[incFive, incFive, incFive].forEach(
  fn => fn()
);

注意每次我们调用 incFive 时我们是如何 运行 连接代码 1 + 5 的吗?如果您 运行 此代码,您将看到

Adding 1 + 5
Result is 6
Adding 1 + 5
Adding 1 + 5
Adding 1 + 5

虽然函数只定义了一次,但函数中的代码一共是“运行”4次。


这就是 Observable 的工作原理! subscribe 对 运行 observable 说,所以如果你订阅 10 次,你 运行 observable 10 次。

当然,如果您处理的是“热”可观察对象,情况会有所不同。使用 concat 重复监听按钮舔(例如)的可观察对象没有多大意义。但是,使用 concat 从服务器请求更新更有意义。

定义一个 observable 一次并能够重新使用它真是太好了。上面,“再调用 3 次”下面的行可以改写为:

// Redefine 3 more times
[addTwoNumbers(1)(5),addTwoNumbers(1)(5), addTwoNumbers(1)(5)].forEach(
  fn => fn()
);

但这很麻烦!另外,有时你想在一个地方定义一个函数,然后在一个单独的地方调用它。到那时你可能不知道如何重新定义它。可观察对象也是如此。