Javascript 呼叫下一个接线员时主题顺序错误
我有一个主题,然后我在上面应用Javascript 呼叫下一个接线员时主题顺序错误,javascript,rxjs,reactive,Javascript,Rxjs,Reactive,我有一个主题,然后我在上面应用map(x=>x/*不是真正的函数*/)操作符。但是map操作符有一些副作用,在某些情况下它会发出新的值 : const sub=新主题(); 常数emits=[]; 常量映射=[]; const emit$=sub.asObservable().subscribe(x=>emit.push(x)); const data$=sub.asObservable()管道( 地图(x=>{ 返回x; }), 点击(x=>mapped.push(x)), 点击(x=>{ 如
map(x=>x/*不是真正的函数*/)
操作符。但是map操作符有一些副作用,在某些情况下它会发出新的值
:
const sub=新主题();
常数emits=[];
常量映射=[];
const emit$=sub.asObservable().subscribe(x=>emit.push(x));
const data$=sub.asObservable()管道(
地图(x=>{
返回x;
}),
点击(x=>mapped.push(x)),
点击(x=>{
如果(x%2==0){
次建议修正案(2333);
}
如果(x==2333){
次建议修正案(1111);
}
})
);
常量数据=[];
数据$.subscribe(x=>{
数据推送(x);
});
次建议修正案(1);
次建议修正案(2);
设置超时(()=>{
log('emits:',emits);
log('mapped:',mapped);
log('datas:',datas);
}, 10);
当输入序列为[1,2]时,对象的订户将接收[1,2,2333,1111],但映射的可观察对象的订户将接收[1,1111,2333,2]
更新:
我将副作用移动到点击操作符中,并将映射的排放存储到一个数组中,然后结果变成:
emits: [1, 2, 2333, 1111]
mapped: [1, 2, 2333, 1111]
datas: [1, 1111, 2333, 2]
以下是问题:
这是正确的行为吗
我应该在运算符中发出新值吗
这里的行为是非直观的,因为Subject
s与普通的可观察对象具有不同的语义
根据,Subject
s的语义类似于EventEmitter
s:它们在内部跟踪订阅者,并在发出新事件时同步调用所有侦听器
在您的示例中,tap
操作符正在同步地将新事件馈送到数据$
可观察对象中。因此,每次调用next()
,它都会在tap()
处理程序返回之前,立即触发新值的管道链
为了更好地观察此行为,请在点击操作符进入和退出时记录日志,并打印堆栈
当您运行该示例时,您将看到管道
链在1111
和2333
中运行,然后最后一次点击2
的处理程序返回。您还将看到1111
和2333
事件的调用堆栈仍然包含管道
/点击
调用2
事件
为什么emit$
observable具有不同顺序的事件emit$
直接订阅到主题
,因此只要调用next
,它的侦听器就会同步运行。从emit$
的角度来看,next
调用的顺序是[1,22331111]
另一方面,datas$
必须等到最后一个tap()
操作符返回后才能调用订户。因此,当2
事件通过最后的点击时,datas$
直到1111
和2333
的处理完成后才会看到它
是否应在运算符中发出新值?
一般来说,没有。为什么?因为很难理解发生了什么,即使是在这样一个相对简单的例子中(所有东西都是同步调用的)
想象一下,如果一个普通的非观察对象不知何故被包括在这个例子中。操作的顺序将非常难以跟踪,而且callstack对于调试也变得不那么有用,因为它会在事件循环的每个滴答声中被清除
通常,如果您确定需要这种精确的语义,则仅在运算符中发出新值。如果有一种方法可以表示您的逻辑,而不需要这样做(例如,使用普通的非主体观察),或者甚至更好,根本不需要使用Subject
s,那么对代码进行推理就会容易得多。如果我使用setTimeout(()=>sub.next(value),0)
来发出运算符中的值,我已经更新了示例代码。这个问题可以这样解决吗?这里没有真正的“问题”:这就是Subject
的工作原理。如果您使用setTimeout
调度next
调用,则现在存在竞争条件:我们不知道在tap
中调度的setTimeout
处理程序是否会在记录数组的setTimeout
之前运行。使用设置超时
将导致发射
,映射
,以及数据
具有相同的顺序。