Javascript RxJS:如何构建反应式观察者树?

Javascript RxJS:如何构建反应式观察者树?,javascript,rxjs,reactive-programming,Javascript,Rxjs,Reactive Programming,我试图使用反应式编程来创建一组可重用的组件,以组装处理树,如决策树 树中的每个节点都可以有子节点。当前节点可以按顺序将当前流值传递给子节点,并对结果执行一些操作,决定是继续下一个子节点,还是只返回上游 parent | +----+-----+ O1 O2 O3 | +--+--+ C1 C2 C3 假设我有一个可观察到的事件。我想将一组观察者序列应用于事件,并确定结果: event stream: --e1-----

我试图使用反应式编程来创建一组可重用的组件,以组装处理树,如决策树

树中的每个节点都可以有子节点。当前节点可以按顺序将当前流值传递给子节点,并对结果执行一些操作,决定是继续下一个子节点,还是只返回上游

       parent
         |
    +----+-----+
    O1   O2    O3
    |
 +--+--+
C1  C2 C3
假设我有一个可观察到的事件。我想将一组观察者序列应用于事件,并确定结果:

event stream:    --e1---------e2--------e3------------------------->
observer stream: --O1---O2---O3----|-->
                        vvvvvvvvvvvv ????? vvvvvvvvvvvvvvv
                 --O1(e1)--O2(e1)--O3(e1)--O1(e2)--O2(e2)--O3(e3)-->
可能吗?这应该是可组合的,因此,例如,O1可以由子观察者组成
[C1、C2、C3]
,它们将发出如下流:

                 --C1(O1(e1))--C2(O1(e1))--C3(O1(e1))--O2(e1)-->
O1需要能够阻止其子项发出值。例如,如果C1返回故障代码,我们不应该执行C2。然后O1将发出一个值,父级将决定是“发出”O1的结果,还是继续O2

这说明了我的目的,您需要打开控制台来查看错误

我的计划是使用takeUntil()和takeLast()等方法来执行“继续或返回”逻辑,如下所示:

// apply event to children until one of them returns 'SUCCESS'
nodeStream(childStreams) {
    return eventStream // Where does this come from?
        .concatMap(childStreams)
        .takeWhile(event => event.result !== 'SUCCESS')
        .takeLast();
}
我已经做到了这一点,但我无法思考如何连接观察者(O1等):

我一直在琢磨如何运用这些孩子。我希望能够构建多个节点流的层次结构(参见上面的ascii图),因此显然上面的示例不起作用,因为每个节点的源流和子流在构建时都不存在:

let c1 = nodeStream();
let c2 = nodeStream();
let c3 = nodeStream();
let o1 = nodeStream([c1, c2, c3]);
let o2 = nodeStream();
let o3 = nodeStream();
let parent = nodeStream([o1, o2, o3]);

eventSource.subscribe(parent);
我使用的是RxJS,但我认为解决方案与所有反应式库类似

更新
对于任何好奇的人,我现在通过让树中的每个节点返回一个承诺来解决这个问题。对于这种类型的结构,promise模型更容易推理。然后,我用一个
Rx.Observable.fromPromise()

将整棵树包装起来,说明你具体在问什么(我不太清楚,所以请原谅我误解了什么),你的观察者是对的

下面的内容应该会给你一些接近你想要的东西

var observerArray = [f1, f2, f3];
return this.eventSource
           .concatMap(function(Ex){
                      return observerArray.map(function (fx){return fx(Ex);});
                    })
您的输出应该是
f1(E1)、f2(E1)、f3(E1)、f1(E2)、f2(E2)、f3(E2)….

更新: 刚才看到你在问题中添加了细节。答案是一样的。所发生的不是数组,而是树数据结构。只要该树数据结构有一个映射函数,该函数遍历树并收集数组中的所有节点,就可以使用相同的代码

var observerTree = //some object or library that implements a tree structure;
return this.eventSource
           .concatMap(function(Ex){
                      return observerTree.map(function (fx){return fx(Ex);});
                    })

现在,如何编写映射取决于树数据结构的方式,但它应该非常简单。

您可能已经知道的一些问题:

你的孩子实际上必须被分成两部分:主体和结果序列。关于这个问题,你需要直接打电话给onNext

var childOneSubject = new Rx.Subject();
var childOneSequence = childOneSubject.map(e => e + '1');
除此之外,您的孩子是热的,这意味着连接他们不会导致预期的结果(child1和child2同时启动-因为您只在child1完成后订阅child2,您只会收到child2的onComplete,onNext在订阅之前已经启动)

你能不能把它转过来,把结果来源变成一个主题,让childOne和childTwo在它上面打个比方

现在我正在发布eventSource,以避免错过它发出的一个事件。从上一个代码片段中,我假设您可以事先准备好所有内容

也许这没有考虑到你提出的其他观点——我明天再看,已经晚了

更新

好的,这将不起作用,因为您需要按顺序执行子对象,以可能阻止后面的子对象运行

更多来自codepen的问题(这对我来说是个错误): 你为什么要使用主题呢?你不能直接映射到可观察的输入吗?即

// child
run(eventSource) {
  return eventSource.map(t => {
    t.state.v = t.state.v + this.suffix;
    t.result = this.status;

    return t;
  });
}
我在这里简化了很多,但也许这可以奏效:


代理流有点棘手:

var source=Rx.Observable.merge(
Rx.Observable.fromEvent(document.getElementById(“first”),“click”).map(()=>({“firstSourceData”:“first source any data”})),
Rx.Observable.fromEvent(document.getElementById(“second”),“click”).map(()=>({“secondSourceData”:“secondsourceanydata”}))
);
var sourceProxy=new Rx.Subject();
var firstStream=sourceProxy.map(()=>{
返回{“firstStreamData”:“任何数据的第一个子项”}
}).publish();
connect();
var secondStream=sourceProxy.map(()=>{
返回{“secondStreamData”:“任何数据的第二个子项”}
}).publish();
secondStream.connect();
source.flatMap(函数(事件){
var first=firstStream.take(1.publishLast();
var second=secondStream.take(1.publishLast();
返回Rx.Observable.defer(函数(){
首先,连接();
第二,连接();
sourceProxy.onNext(事件);
返回Rx.Observable.empty();
})
.合并(第一)
.合并(第二)
//在该位置之后,您可以根据需要合成或聚合所有三个值
.reduce((结果、数据)=>{
返回Object.assign(结果、数据、事件);
})
})
.订阅(功能(数据){
控制台日志(数据);
});

你能举一个你想申请的观察员的例子吗?@Nupf-我补充了更多细节,我仍然不完全理解。关于最后一点:如果问题是父级之前不存在子级:传入空的childstreams数组以创建父级,然后填充该childstreams数组。由于Rx仅在订阅时执行concatMap,因此它将看到最新版本的childstreamsarray@Nupf-我添加了一些更通顺的代码,可以更好地解释。非常感谢,我希望明天再看一看-我现在会尝试应用你的评论这里的问题是代码需要是可组合的。。。孩子们也需要有孩子。在你的sim卡中
function node(eventSource) {  
  var resultSource = new Rx.Subject();
  eventSource.subscribe(e => resultSource.onNext(e + '1')); // child1 
  eventSource.subscribe(e => resultSource.onNext(e + '2')); // child2
  return resultSource;
}

// Expected Output:
// onNext: event1
// onNext: event2
// onCompvare
function testEvent() {
  // This will be hot in the real world
  var eventSource = Rx.Observable.of('event').publish();
  var resultSource = node(eventSource);
  resultSource.subscribe(
    function(x) {
      console.log('onNext:', x);
    },
    function(e) {
      console.log('onError:', e);
    },
    function() {
      console.log('onCompvare');
    }
  );
  eventSource.connect();

}
testEvent();    
// child
run(eventSource) {
  return eventSource.map(t => {
    t.state.v = t.state.v + this.suffix;
    t.result = this.status;

    return t;
  });
}
var eventSource = Rx.Observable.of('event', 'event', 'event');
//children abstracted as just their suffix & status
var childSource = Rx.Observable.of(
  {suffix: 1, status: 1},
  {suffix: 2, status: 0},
  {suffix: 3, status: 1}  
); 

function runChildren(eventSource) {
  return childSource.concatMap(child => {
    if(child.status == 1) return eventSource.map(event => event + child.suffix);
    else return Rx.Observable.throw(child.suffix);
  })
  .catch(error => Rx.Observable.empty());
}


var node = eventSource.concatMap(event => {
  var childEventSource = Rx.Observable.of(event);
  return runChildren(childEventSource);
});

node.subscribe(event => console.log(event));