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
    之前运行。使用
    设置超时
    将导致
    发射
    映射
    ,以及
    数据
    具有相同的顺序。