Субъект находится в неправильном порядке, когда следующий оператор - PullRequest
0 голосов
/ 06 мая 2018

У меня есть тема, затем я применяю оператор map(x => x/*not the real function*/). Но у оператора карты есть некоторые побочные эффекты, в некоторых случаях он выдаст новое значение.

Вот пример :

const sub = new Subject();
const emits = [];
const mapped = [];
const emit$ = sub.asObservable().subscribe(x => emits.push(x));
const data$ = sub.asObservable().pipe(
    map(x => {
        return x;
    }),
    tap(x => mapped.push(x)),
    tap(x => {
        if (x % 2 === 0) {
            sub.next(2333);
        }
        if (x === 2333) {
            sub.next(1111);
        }
    })
);
const datas = [];
data$.subscribe(x => {
    datas.push(x);
});
sub.next(1);
sub.next(2);
setTimeout(() => {
    console.log('emits: ',emits);
    console.log('mapped: ', mapped);
    console.log('datas: ', datas);
}, 10);

когда входной последовательностью является [1, 2], подписчик субъекта получит [1, 2, 2333, 1111], но подписчик сопоставленной наблюдаемой получит [1, 1111, 2333, 2].

UPDATE:

Я перемещаю побочные эффекты в оператор tap и сохраняю сопоставленные выбросы в массив, а затем получаю результат:

emits: [1, 2, 2333, 1111]
mapped: [1, 2, 2333, 1111]
datas: [1, 1111, 2333, 2]

Вот вопросы:

  1. Это правильное поведение?
  2. Должен ли я выдавать новое значение в операторах?

1 Ответ

0 голосов
/ 06 мая 2018

Поведение здесь неинтуитивно, потому что Subject имеют семантику, отличную от обычных Observables.

В документации , Subject s имеют семантику, аналогичную EventEmitter s: они отслеживают своих подписчиков внутри и синхронно вызывают всех слушателей , когда новое событие отправлено.

В вашем примере оператор tap синхронно передает новые события в data$ Observable. Поэтому каждый раз, когда он вызывает next(), он немедленно запускает цепочку pipe для нового значения , прежде чем возвращается обработчик tap().

Чтобы лучше наблюдать это поведение, в этом примере регистрирует, когда оператор tap входит и выходит, а также печатает стек.

Когда вы запустите пример, вы увидите, что цепочка pipe работает для 1111 и 2333 до того, как вернется окончательный обработчик tap для 2. Вы также увидите, что стек вызовов событий 1111 и 2333 по-прежнему содержит вызовы pipe / tap для события 2.

Почему наблюдаемая emit$ имеет события в другом порядке? emit$ подписан непосредственно на Subject, поэтому его слушатель синхронно запускается, как только вызывается next. С точки зрения emit$, порядок вызовов next составляет [1, 2, 2333, 1111].

datas$, с другой стороны, должен ждать, пока окончательный оператор tap() не вернется, прежде чем вызывать абонента. Поэтому, когда событие 2 проходит через финальный tap, datas$ не видит его, пока не завершится обработка 1111 и 2333.

Должны ли вы выдавать новые значения в операторах?

Обычно нет. Зачем? Потому что очень трудно понять, что происходит, даже в относительно простом примере, подобном этому (где все называется синхронно).

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

В общем случае генерировать новые значения в операторах, только если вы уверены, что вам нужна точная семантика . Если есть способ представить свою логику без этого (например, с помощью обычных не-предметных наблюдаемых) или, что еще лучше, вообще без использования Subject s, тогда будет гораздо проще рассуждать о коде.

...