组合Observables A和B:只有在A有数据时才会触发;如果是数据或默认则从B获取

时间:2015-01-31 15:45:42

标签: javascript rxjs

我有一个应用程序通过串口与设备通信。每个发送的命令由包含状态/答案的数据事件回答。基本上有更改设备的命令和只返回状态的命令。每次最后一个命令被应答时(所以在接收数据时)应用程序应发送下一个命令或作为默认查询状态。我试图用rxjs来模拟它。 我的想法是有一个命令observable和一个从data事件派生的数据observable。这两个应该以这样的方式组合,结果observable仅在有数据时发出值,并将其与命令或默认命令(请求状态)组合,如果命令流中没有命令。

data:     ---------d---d-----d---------d------d-------
command:  --c1---c2----------------------c3-----------
______________________________________________________
combined  ---------c1--c2----dc--------dc-----c3

dc是默认命令。也不应该丢失任何命令。

目前我有一个匿名主题的实现,我自己实现了observable和observer。从数组中的命令流收集命令,订阅数据事件,使用onNext手动发布数据并从数组或默认发送下一个命令。这有效,但我觉得这可以用rxjs更优雅地表达。

一种方法是使用单独的default_command流,每100ms重复一次默认命令。这与命令流合并,然后用数据流压缩。这里的问题是合并的命令流,因为它堆积了默认命令,但默认命令只应用,如果没有其他命令。

3 个答案:

答案 0 :(得分:0)

我能想到的只有:

  • 订阅命令流并将结果排队(在数组中)
  • 将映射操作应用于将从队列中提取的数据流(如果队列为空,则使用默认值)。

我们可以将它包装成一个通用的可观察运算符。我的姓名不好,所以我称之为zipWithDefault

Rx.Observable.prototype.zipWithDefault = function(bs, defaultB, selector) {
  var source = this;
  return Rx.Observable.create(function(observer) {
    var sourceSubscription = new Rx.SingleAssignmentDisposable(),
      bSubscription = new Rx.SingleAssignmentDisposable(),
      subscriptions = new Rx.CompositeDisposable(sourceSubscription, bSubscription),
      bQueue = [],
      mappedSource = source.map(function(value) {
        return selector(value, bQueue.length ? bQueue.shift() : defaultB);
      });

    bSubscription.setDisposable(bs.subscribe(
      function(b) {
        bQueue.push(b);
      },
      observer.onError.bind(observer)));

    sourceSubscription.setDisposable(mappedSource.subscribe(observer));

    return subscriptions;
  });
};

并像这样使用它:

combined = dataStream
  .zipWithDefault(commandStream, defaultCommand, function (data, command) {
    return command;
  });

答案 1 :(得分:0)

我认为sample运营商是您最好的选择。不幸的是,它没有内置的默认值,因此你必须从现有的运算符中推出自己的值:

Rx.Observable.prototype.sampleWithDefault = function(sampler, defaultValue){
var source = this;

return new Rx.AnonymousObservable(function (observer) {
      var atEnd, value, hasValue;

      function sampleSubscribe() {
          observer.onNext(hasValue ? value : defaultValue);
          hasValue = false;
      }

      function sampleComplete() {
          atEnd && observer.onCompleted();
      }

      return new Rx.CompositeDisposable(
        source.subscribe(function (newValue) {
          hasValue = true;
          value = newValue;
        }, observer.onError.bind(observer), function () {
          atEnd = true;
        }),
        sampler.subscribe(sampleSubscribe, observer.onError.bind(observer), sampleComplete)
      );
    }, source);
}

您可以使用controlled运算符来实现排队行为。因此,您的最终数据链将如此:

var commands = getCommandSource().controlled();
var pipeline = commands
  .sampleWithDefault(data, defaultCommand)
  .tap(function() { commands.request(1); });

以下是一个完整的例子:

Rx.Observable.prototype.sampleWithDefault = function(sampler, defaultValue) {
  var source = this;

  return new Rx.AnonymousObservable(function(observer) {
    var atEnd, value, hasValue;

    function sampleSubscribe() {
      observer.onNext(hasValue ? value : defaultValue);
      hasValue = false;
    }

    function sampleComplete() {
      atEnd && observer.onCompleted();
    }

    return new Rx.CompositeDisposable(
      source.subscribe(function(newValue) {
        hasValue = true;
        value = newValue;
      }, observer.onError.bind(observer), function() {
        atEnd = true;
      }),
      sampler.subscribe(sampleSubscribe, observer.onError.bind(observer), sampleComplete)
    );
  }, source);
}

var scheduler = new Rx.TestScheduler();

var onNext = Rx.ReactiveTest.onNext;
var onCompleted = Rx.ReactiveTest.onCompleted;

var data = scheduler.createHotObservable(onNext(210, 18), 
                                         onNext(220, 17),
                                         onNext(230, 16),
                                         onNext(250, 15),
                                         onCompleted(1000));

var commands = scheduler.createHotObservable(onNext(205, 'a'), 
                                             onNext(210, 'b'), 
                                             onNext(240, 'c'), 
                                             onNext(400, 'd'), 
                                             onCompleted(800))
  .controlled(true, scheduler);


var pipeline = commands
  .sampleWithDefault(data, 'default')
  .tap(function() {
    commands.request(1);
  });

var output = document.getElementById("output");

pipeline.subscribe(function(x) {
  
  var li = document.createElement("li");
  var text = document.createTextNode(x);
  
  li.appendChild(text);
  output.appendChild(li);
    
  
});

commands.request(1);

scheduler.start();
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/2.5.2/rx.all.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/2.5.2/rx.testing.js"></script>
<div>
  
  <ul id="output" />
  
</div>

答案 2 :(得分:0)

这可以通过使用scan函数来解决。在累计值中,存储的命令尚未收到任何数据响应。

var result = Rx.Observable
   .merge(data, command)
   .scan(function (acc, x) {
        if (x === 'd') {
            acc.result = acc.commands.length > 0 ? acc.commands.shift() : 'dc';
        } else {
            acc.result = '';
            acc.commands.push(x);
        }
        return acc;
   }, {result: '', commands: []})
   .map(function (x) {
      return x.result;
   })
   .filter(function (x) {
       return x !== '';
   });

请在此处找到更详细的信息:http://jsbin.com/tubade/edit?html,js,console