RxJS

时间:2017-06-01 06:46:29

标签: javascript rxjs rxjs5

我想创建一个

的流
  1. 拆分值并在单独的流上处理每个部分
  2. 每个流都会转换数据,我无法控制应用的转换
  3. (重新)将部分值与其对应的计数器部分
  4. 连接起来

    我想这样做的原因是为了确保价值的完整性。或者至少在某些方面。

    因为每个流都可能有一些异步操作,所以在加入流时它们不会按顺序出现。使用某种concat()也不起作用,因为它会阻止所有传入的值。处理应该并行进行。

    举例说明:

                                o
                                |
                                | [{a1,b1}, {a2,b2}, ...]
                                |
                                +
                               / \
                       {a<x>} /   \ {b<x>}
                             /     \
                            |       |
                            |       + async(b<x>) -> b'<x>
                            |       |
                            \       /
                             \     /
                              \   /
                               \ /
                                + join(a<x>, b'<x>)
                                |
                                | [{a1,b'1}, {a2,b'2}, ...]
                                |
                           (subscribe)
    

    我知道这可以通过result selector函数完成。 E.g。

    input$.mergeMap(
      ({a, b}) => Rx.Observable.of(b).let(async), 
      ({a}, processedB) => ({a, b:processedB})
    );
    

    但是,(a)这将导致async始终为每个值设置/拆除。我希望部分流只能初始化一次。另外,(b)这只适用于一个异步流。

    我也尝试使用window*,但无法弄清楚如何重新加入值。还尝试使用goupBy但没有运气。

    修改

    这是我目前的尝试。它有上述问题(a)。 Init...Completed...只应记录一次。

    &#13;
    &#13;
    const doSomethignAsync = data$ => {
      console.log('Init...') // Should happen once.
      return data$
        .mergeMap(val => Rx.Observable.of(val.data).delay(val.delay))
        .finally(() => console.log('Completed...')); // Should never happen
    };
    
    const input$ = new Rx.Subject();
    const out$ = input$
      .mergeMap(
        ({ a, b }) => Rx.Observable.of(b).let(doSomethignAsync),
        ({ a }, asyncResult ) => ({ a, b:asyncResult })
      )
      
      .subscribe(({a, b}) => {
        if (a === b) { 
          console.log(`Re-joined [${a}, ${b}] correctly.`);
        } else {
          console.log(`Joined [${a}, ${b}]...`); // Should never happen
        }
      });
    
    
    input$.next({ a: 1, b: { data: 1, delay: 2000 } });
    input$.next({ a: 2, b: { data: 2, delay: 1000 } });
    input$.next({ a: 3, b: { data: 3, delay: 3000 } });
    input$.next({ a: 4, b: { data: 4, delay: 0 } });
    &#13;
    <script src="https://unpkg.com/rxjs/bundles/Rx.min.js"></script>
    &#13;
    &#13;
    &#13;

1 个答案:

答案 0 :(得分:1)

这是一个相当复杂的问题需要解决,具体如何操作将取决于您的用例中非常具体的细节。

那就是说,这是一种可能的方式做出一系列假设。它被设置为有点通用,就像与let一起使用的自定义运算符。

(旁注:我把它命名为#34; collat​​e&#34;但这是一个很糟糕且极具误导性的名字,但是没有时间来命名......)

const collate = (...segments) => source$ =>
  source$
    .mergeMap((obj, index) => {
      return segments.map(({ key, work }) => {
        const input = obj[key];
        const output$ = work(input);

        return Observable.from(output$).map(output => ({
          index,
          result: { [key]: output }
        }))
      })
    })
    .mergeAll()
    .groupBy(
      obj => obj.index,
      obj => obj.result,
      group$ => group$.skip(segments.length - 1)
    )
    .mergeMap(group$ =>
      group$.reduce(
        (obj, result) => Object.assign(obj, result),
        {}
      )
    );

这是一个用法示例:

const result$ = input$.let(
  collate({
    key: 'a',
    work: a => {
      // do stuff with "a"
      return Observable.of(a).map(d => d + '-processed-A');
    }
  }, {
    key: 'b',
    work: b => {
      // do stuff with "b"
      return Observable.of(b).map(d => d + '-processed-B');
    }
  })
);

给定输入{ a: '1', b: '1 }它将输出{ a: '1-processed-A', b: '1-processed-B' }等等,在尽可能多地同时执行时正确分组 - 它唯一的缓冲就是将特定输入的所有段匹配在一起。

这是一个正在运行的演示https://jsbin.com/yuruvar/edit?js,output

击穿

可能有更明确/更简单的方法,特别是如果你可以对某些东西进行硬编码而不是使它们通用。但是,让我们分解我的所作所为。

const collate = (...segments) => source$ =>
  source$
    // for every input obj we use the index as an ID
    // (which is provided by Rx as autoincrementing)
    .mergeMap((obj, index) => {
      // segments is the configuration of how we should
      // chunk our data into concurrent processing channels.
      // So we're returning an array, which mergeMap will consume
      // as if it were an Observable, or we could have used
      // Observable.from(arr) to be even more clear
      return segments.map(({ key, work }) => {
        const input = obj[key];
        // the `work` function is expected to return
        // something Observable-like
        const output$ = work(input);

        return Observable.from(output$).map(output => ({
          // Placing the index we closed over lets us later
          // stitch each segment back to together
          index,
          result: { [key]: output }
        }))
      })
    })
    // I had returned Array<Observable> in mergeMap
    // so we need to flatten one more level. This is
    // rather confusing...prolly clearer ways but #YOLO
    .mergeAll()
    // now we have a stream of all results for each segment
    // in no guaranteed order so we need to group them together
    .groupBy(
      obj => obj.index,
      obj => obj.result,
      // this is tough to explain. this is used as a notifier
      // to say when to complete() the group$, we want complete() it
      // after we've received every segment for that group, so in the
      // notifier we skip all except the last one we expect
      // but remember this doesn't skip the elements downstream!
      // only as part of the durationSelector notifier
      group$ => group$.skip(segments.length - 1)
    )
    .mergeMap(group$ =>
      // merge every segment object that comes back into one object
      // so it has the same shape as it came in, but which the results
      group$.reduce(
        (obj, result) => Object.assign(obj, result),
        {}
      )
    );

我没有想到或担心错误处理/传播可能如何工作,因为它高度依赖于您的用例。如果您无法控制每个段的处理,那么还会包含某种超时和.take(1),否则您可能会泄漏订阅。