与其他“FRP”库不同,Rx不会阻止毛刺:使用时间不匹配的数据调用回调。有没有好办法解决这个问题?
作为一个例子,假设我们有一系列昂贵的计算来自单个流(例如,而不是_.identity,下面,我们做一个排序,或ajax提取)。我们做的很明显,以避免重新计算昂贵的东西。
sub = new Rx.Subject();
a = sub.distinctUntilChanged().share();
b = a.select(_.identity).distinctUntilChanged().share();
c = b.select(_.identity).distinctUntilChanged();
d = Rx.Observable.combineLatest(a, b, c, function () { return _.toArray(arguments); });
d.subscribe(console.log.bind(console));
sub.onNext('a');
sub.onNext('b');
第二个事件最终会导致一些错误状态:我们得到三个事件,而不是一个,这会浪费一堆cpu并要求我们明确解决不匹配的数据。
这个特定的例子可以通过删除distinctUntilChanged来解决,如果输入没有改变,可以编写一些不稳定的scan()函数来传递前一个结果。然后你可以压缩结果,而不是使用combineLatest。它很笨拙但可行。
但是,如果在任何地方存在异步,例如一个ajax调用,然后zip不起作用:ajax调用将同步(如果缓存)或异步完成,所以你不能使用zip。
修改
尝试使用更简单的示例澄清所需的行为:
你有两个流,a和b。 b取决于a。 b是异步的,但是浏览器可以缓存它,因此它可以独立地更新,也可以同时更新。因此,浏览器中的特定事件可能会导致以下三种情况之一:更新; b更新; a和b都更新。所需的行为是在所有三种情况下都只调用一次回调(例如渲染方法)。
zip不起作用,因为当a或b单独触发时,我们不会收到来自zip的回调。 combineLatest不起作用,因为当a和b一起发射时,我们会得到两个回调。
答案 0 :(得分:5)
概念
a和b更新
a
和b
都是可观察的,在Rx中不作为基元存在。
没有无损的一般运算符可以定义,以决定何时收到来自a
的通知是否应将其传递到下游或延迟直到它收到来自b
的通知。 Rx中的通知本身并不带有“两个”语义,或Rx语法之外的任何语义。
此外,Rx的串行合同阻止运营商利用重叠通知来实现这一目标。 (虽然我怀疑依靠竞争条件不是你想要的方法。)
见Rx Design Guidelines中的§§4.2,6.7。
因此,我在上面所说的“没有无损的,可以定义的一般运算符......”是给定两个具有独立通知的a
和b
的可观察者,任何尝试的运算符要确定何时收到来自a
或b
的通知是否必须立即推送或等待“其他”值,必须依赖于任意时间。这是猜测。因此,这个假设的运算符必须丢弃值(例如,DistinctUntilChanged
或Throttle
),或丢弃时间(例如,Zip
或Buffer
),尽管可能是两者的某种组合。
因此,如果代理能够单独推送a
,或单独b
,或a
和b
作为通知单元,那么它就是开发人员的有责任明确通知单元的概念。
需要3种状态:a | b | {A,B}
(请原谅我糟糕的JS)
var ab = function(a, b) { this.a = a; this.b = b; }
sub.onNext(new ab('a')); // process a alone
sub.onNext(new ab('a', 'b')); // process a and b together
sub.onNext(new ab(null, 'c')); // process c alone
observable查询的形状不再重要。必须定义观察者以接受此数据类型。生成器有责任根据其内部状态的语义应用任何必要的缓冲或计时计算,以便为其观察者生成正确的通知。
顺便说一句,感谢您在编辑中提供简单的解释(无论如何我似乎都很清楚)。我第一次听到this Rx forum discussion中的“小故障”。如你所见,它从未真正结束。现在我想知道OP的问题是否真的如此简单,假设我当然正确地理解了你的问题。 : - )
<强>更新强>
这是另一个相关的讨论,包括我对Rx不是FRP的原因的一些看法: