我有一个主题,然后我在其上应用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]。
更新:
我将副作用移到tap
运算符中,并将映射的发射存储到数组中,然后结果变为:
emits: [1, 2, 2333, 1111]
mapped: [1, 2, 2333, 1111]
datas: [1, 1111, 2333, 2]
以下是问题:
答案 0 :(得分:2)
此处的行为不直观,因为Subject
与普通的Observable具有不同的语义。
每the documentation,Subject
的语义类似于EventEmitter
:它们在内部跟踪其订阅者,同步调用所有侦听器何时发出新事件。
在您的示例中,tap
运算符正在同步将新事件提供给data$
Observable。因此,每当它调用next()
时,它会在pipe
处理程序返回之前立即触发新值的tap()
链。
为了更好地观察此行为,this example会在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
处理完成之前看不到它。
一般来说,没有。为什么?因为很难理解发生了什么,即使是在这样一个相对简单的例子中(所有事情都是同步调用的)。
想象一下,如果一个普通的非主题Observable以某种方式包含在这个例子中。操作的顺序非常难以跟踪,并且callstack对于调试变得不那么有用,因为它会在事件循环的每个tick中被清除。
通常,只有在确定您需要这种确切的语义时,才会在运算符中发出新值。如果有一种方法可以在不这样做的情况下表示你的逻辑(例如使用普通的非主题Observable),甚至更好,而不使用Subject
s,那么推理代码会更容易。< / p>