在操作员中呼叫下一个时,主题的顺序错误

时间:2018-05-06 09:52:33

标签: javascript rxjs reactive

我有一个主题,然后我在其上应用map(x => x/*not the real function*/)运算符。但是地图运算符有一些副作用,在某些情况下会发出新的值。

Here's the example

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]。

更新:

我将副作用移到tap运算符中,并将映射的发射存储到数组中,然后结果变为:

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

以下是问题:

  1. 这是正确的行为吗?
  2. 我应该在运营商中发布新值吗?

1 个答案:

答案 0 :(得分:2)

此处的行为不直观,因为Subject与普通的Observable具有不同的语义。

the documentationSubject的语义类似于EventEmitter:它们在内部跟踪其订阅者,同步调用所有侦听器何时发出新事件。

在您的示例中,tap运算符正在同步将新事件提供给data$ Observable。因此,每当它调用next()时,它会在pipe处理程序返回之前立即触发新值tap()链。

为了更好地观察此行为,this example会在tap运算符进入和退出时记录,并打印堆栈。

运行示例时,您会看到在pipe的最终1111处理程序返回2333tap之前,2链会运行。您还会看到11112333事件的调用堆栈仍包含pipe事件的tap / 2次调用。

为什么emit$可观察事件的事件顺序不同? emit$直接订阅Subject,因此只要调用next,其侦听器就会同步运行。从emit$的角度来看,next来电的顺序为[1, 2, 2333, 1111]

另一方面,

datas$必须等到最后tap()运算符返回,然后才能调用订阅者。因此,当2事件通过最终tap时,datas$11112333处理完成之前看不到它。

您应该在运营商中发布新值吗?

一般来说,没有。为什么?因为很难理解发生了什么,即使是在这样一个相对简单的例子中(所有事情都是同步调用的)。

想象一下,如果一个普通的非主题Observable以某种方式包含在这个例子中。操作的顺序非常难以跟踪,并且callstack对于调试变得不那么有用,因为它会在事件循环的每个tick中被清除。

通常,只有在确定您需要这种确切的语义时,才会在运算符中发出新值。如果有一种方法可以在不这样做的情况下表示你的逻辑(例如使用普通的非主题Observable),甚至更好,而不使用Subject s,那么推理代码会更容易。< / p>