Я кодирую игру, в которой персонаж может стрелять из своего оружия. Я хочу, чтобы разные вещи случались, когда игрок пытался стрелять, в зависимости от того, есть ли у них боеприпасы.
Я уменьшил свою проблему до следующего кода (кстати, я не уверен, почему функция 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()
должен работать?
Это действительно работает правильно. Путаница исходит от оператора 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
который более очевиден:
fire$
. Поскольку все это синхронно и цепочкаfire$
изменяет hasAmmo, когдаnoAmmo$
запускается сразу после этого, условие фильтра удовлетворяется, что я не ожидаю. Решение здесь состоит в том, чтобы изменить порядок подписок,noAmmo$
идетnoAmmo$
поскольку он не может повлиять наfire$
, а затем наfire$
. Я рад, что это относительно простой случай, я не могу себе представить, что бы я сделал, если бы все они могли повлиять друг на друга! PS: в моем реальном кодеfrom([true])
можно наблюдать событие, если оно проясняет ситуацию.