RxJS разделяет родительский наблюдаемый среди разделенных дочерних наблюдаемых

1

Я кодирую игру, в которой персонаж может стрелять из своего оружия. Я хочу, чтобы разные вещи случались, когда игрок пытался стрелять, в зависимости от того, есть ли у них боеприпасы.

Я уменьшил свою проблему до следующего кода (кстати, я не уверен, почему функция sn snippet не работает, поэтому я сделал CodePen, где вы можете попробовать мой код).

const { from, merge } = rxjs;
const { partition, share, tap } = rxjs.operators;

let hasAmmo = true;
const [ fire$, noAmmo$ ] = from([true]).pipe(
  share(),
  partition(() => hasAmmo),
);


merge(
  fire$.pipe(
    tap(() => {
      hasAmmo = false;
      console.log('boom');
    }),
  ),
  noAmmo$.pipe(
    tap(() => {
      console.log('bam');
    }),
  )
).subscribe({
  next: val => console.log('next', val),
  error: val => console.log('error', val),
  complete: val => console.log('complete', val),
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.3.3/rxjs.umd.js"></script>

Когда я запускаю этот код, я получаю следующее:

"boom"
"next" true
"bam"
"next" true
"complete" undefined

Я не понимаю, почему я получаю "bam".

Первая эмиссия идет на fire$ (я получаю "boom"), что имеет смысл, потому что hasAmmo true. Но как побочный эффект fire$ emitting заключается в том, что результат состояния раздела изменяется, что, по-моему, вызывает у меня "bam".

Я не должен вызывать побочные эффекты, которые влияют на partition()?

Или, может быть, есть проблема с тем, как я share() мой родитель наблюдаемый? Возможно, я ошибаюсь, но я бы интуитивно подумал, что fire$ и noAmmo$ внутренне подписываются на родителя, чтобы разделить его, и в этом случае share() должен работать?

Теги:
rxjs
observable

1 ответ

2
Лучший ответ

Это действительно работает правильно. Путаница исходит от оператора partition который в основном является всего лишь двумя операторами filter.

Если вы переписываете его без partition он выглядит так:

const fire$ = from([true]).pipe(
  share(),
  filter(() => hasAmmo),
);

const noAmmo$ = from([true]).pipe(
  share(),
  filter(() => !hasAmmo),
);

Имейте в hasAmmo что изменение hasAmmo не влияет на сам partition. partition действует только тогда, когда он получает значение из своего источника Observable.

Когда вы позже используете merge() он делает две отдельные подписки на две совершенно разные цепочки с двумя отличными from([true]) s. Это означает, что true передается как fire$ и noAmmo$.

Поэтому share() имеет никакого эффекта здесь. Если вы хотите поделиться этим вам придется обернуть from прежде чем использовать его на fire$ и noAmmo$. Если источник Observable только from него, к сожалению, будет еще более запутанным, потому что исходное излучение поступит только к первому подписчику, который будет fire$ позже при использовании в merge:

const shared$ = from([true]).pipe(
  share(),
);

const fire$ = shared$.pipe(...);
const noAmmo$ = shared$.pipe(...);

Последнее, почему вы получаете оба сообщения, - это то, что partition не изменяет значение, которое проходит. Он только решает, какой из возвращенных Observable будет повторно использовать его.

Btw, скорее избегайте partition потому что он, вероятно, будет устаревшим и просто использует filter который более очевиден:

  • 0
    Спасибо за подробный ответ! Первоначальная эмиссия действительно прибудет сначала в fire$ . Поскольку все это синхронно и цепочка fire$ изменяет hasAmmo, когда noAmmo$ запускается сразу после этого, условие фильтра удовлетворяется, что я не ожидаю. Решение здесь состоит в том, чтобы изменить порядок подписок, noAmmo$ идет noAmmo$ поскольку он не может повлиять на fire$ , а затем на fire$ . Я рад, что это относительно простой случай, я не могу себе представить, что бы я сделал, если бы все они могли повлиять друг на друга! PS: в моем реальном коде from([true]) можно наблюдать событие, если оно проясняет ситуацию.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню