Rx - 基于内容拆分Observable(分组直到更改)

时间:2014-12-24 19:31:12

标签: javascript system.reactive reactive-programming rxjs

首先让我先介绍一下这个问题。目标是使用Rx处理带分页的简单搜索屏幕。

在输入端,用户可以添加各种过滤条件,还可以更改当前显示的页面(如果结果集较大)。为简单起见,可以使用{filter,page}对流来建模。

在输出端,结果数据需要显示在表格中,并且需要更新页面数量。这意味着{data,count}对的流。

连接两者的逻辑如下:每当滤波器参数改变时,发送一个'计数'查询服务器以获取页数。每当过滤器参数或页面索引发生变化时,发送一个' select'查询服务器以获取结果数据。在更新屏幕之前,请等待两者都可用。

这是一种以最小的方式模拟这种情况的插件。 s1 / s2 / s3表示过滤参数,p1 / p2 / p3是页面索引,#s1 /#s2 / #s3是' count'过滤器的响应,以及类似于' s2 p3'是一个选择'对给定过滤器和页面组合的响应。你可以看到各种失败的方法,注释掉。

http://plnkr.co/xurmUO

似乎解决方案应该使用像groupBy或window这样的东西,但是它们都不会产生预期的结果。使用groupBy,您可以执行的操作是从输入流创建组,并根据filter参数将输入项分配给组。然后,您只需触发一次'计数'查询创建新组时,触发“选择”。查询组中的每个项目。

这个问题是组会一直持续到源流结束,因此如果重复先前发生的过滤器参数,则不会创建新组。因此,不计算'查询也会发生。 groupByUntil运算符允许指定到期时间,几乎解决所有问题。然后,您可以创建新组(由于输入中的不同过滤器参数)触发当前组的到期。像这样:

inputs
  .groupByUntil(
    function(input) { return input.filter; },
    null,
    function(inputGroup) { 
      var filter = inputGroup.key;
      return inputs.firstOrDefault(function (input) {
        return input.filter !== filter;
      });
    }
  );

不幸的是,根据操作员执行的顺序,这似乎比它应该触发的时间要晚。因此,组边界会被一个人关闭,最后你会得到一个额外的'计数& #39;呼叫。我也尝试使用窗口操作符,它有一个类似的问题:一个额外的项目在关闭之前进入窗口。

我想要的是groupBy的变体,它一次只有一个活动组,使得前一个在收到带有新密钥的项目时到期。你能以某种方式结合现有的运营商来构建这个吗?或者您是否必须返回Observable.create,并使用低级操作执行此操作?

编辑:作为参考,这里是使用自定义运算符的解决方案,使用Observable.create完成。

Rx.Observable.prototype.splitBy = function(keySelector) {
  var source = this;
  var currentGroup = null;
  var lastKey = null;
  return Rx.Observable.create(function(observer) {
    return source.subscribe(
      function(value) {
        var key = keySelector(value);
        // Create a new group if the key changed (or this is the first value)
        if (currentGroup === null || key !== lastKey) {
          // Close previous group
          if (currentGroup !== null) {
            currentGroup.onCompleted();
          }
          lastKey = key;
          // Emit current group
          currentGroup = new Rx.Subject();
          currentGroup.key = key;
          observer.onNext(currentGroup);
        }
        currentGroup.onNext(value);
      },
      // Forward errors/completion to current group and the main stream
      function(error) {
        if (currentGroup !== null) {
          currentGroup.onError(error);
        }
        observer.onError(error);
      },
      function() {
        if (currentGroup !== null) {
          currentGroup.onCompleted();
        }
        observer.onCompleted();
      });
  });
};

1 个答案:

答案 0 :(得分:4)

由于您已发现的原因,groupBy系列运算符无效。你想要flatMapLatest

var count = inputs.flatMapLatest(function (input) {
    // some code that returns an
    // Rx observable that will eventually
    // return the count
    return rxQueryForCount(input.filter);
});
count.subscribe(...);

你会写类似的东西来返回数据页面。

编辑:如果要同步2个查询的结果,则执行以下操作:

var count = inputs
    .distinctUntilChanged(null, function (a, b) {
        // however you decide if your filters have changed...
        return a.filter === b.filter;
    })
    .flatMapLatest(function (input) {
        // some code that returns an
        // Rx observable that will eventually
        // return the count
        return rxQueryForCount(input.filter)
            .startWith(undefined);
    });
var data = inputs
    .flatMapLatest(function (input) {
        return rxQueryForData(input.filter, input.index)
            .startWith(undefined);
    });
var results = Rx.Observable
    .combineLatest(count, data, function (c, d) {
        if (c === undefined || d === undefined) {
            return undefined;
        }

        return { count: c, data: d };
    });
每当查询重新运行时,

结果将为undefined,并且只有在两个查询都有更新结果时才会获取值。您可以在UI逻辑中使用它,甚至只需添加where子句来过滤掉undefined值。