Javascript RxJS中的分支和(重新)连接值对

Javascript RxJS中的分支和(重新)连接值对,javascript,rxjs,rxjs5,Javascript,Rxjs,Rxjs5,我想创建一个 拆分值并在单独的流上处理每个部分 每个流都将转换数据,我无法控制应用的转换 (re-)将部分值与其对应的计数器部分连接起来 我这样做的原因是为了确保值的完整性。或者至少在某种程度上 因为每个流可能有一些异步操作,所以它们在加入流时不会按顺序出现。使用某种concat()也不起作用,因为它会阻止所有传入值。处理应该并行进行 举例说明: o |

我想创建一个

  • 拆分值并在单独的流上处理每个部分
  • 每个流都将转换数据,我无法控制应用的转换
  • (re-)将部分值与其对应的计数器部分连接起来
  • 我这样做的原因是为了确保值的完整性。或者至少在某种程度上

    因为每个流可能有一些异步操作,所以它们在加入流时不会按顺序出现。使用某种
    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)
    
    但是,(a)这将导致始终为每个值设置/删除
    async
    。我希望部分流只初始化一次。此外,(b)这只适用于一个异步流

    我还尝试使用
    窗口*
    ,但无法想出如何再次重新连接值。还试图使用
    goupBy
    ,但运气不佳


    编辑:

    这是我目前的尝试。它有上述问题(a)<代码>初始化…和
    完成…
    只应记录一次

    const doSomethignAsync=data$=>{
    console.log('Init…')//应该发生一次。
    返回数据$
    .mergeMap(val=>Rx.Observable.of(val.data).delay(val.delay))
    .finally(()=>console.log('Completed…');//不应该发生
    };
    const input$=新的Rx.Subject();
    const out$=输入$
    .合并地图(
    ({a,b})=>Rx.Observable.of(b).let(doSomethignAsync),
    ({a},asyncResult)=>({a,b:asyncResult})
    )
    .subscribe({a,b})=>{
    如果(a==b){
    log(`rejoined[${a},${b}]正确。`);
    }否则{
    log(`Joined[${a},${b}]…`);//不应该发生
    }
    });
    输入$.next({a:1,b:{data:1,delay:2000});
    输入$.next({a:2,b:{data:2,delay:1000});
    输入$.next({a:3,b:{data:3,delay:3000});
    输入$.next({a:4,b:{data:4,delay:0})
    
    这是一个需要解决的相当复杂的问题,具体如何解决将取决于未包含的用例的非常具体的细节

    这就是说,这里有一种可能的方法可以做出一系列假设。它有点通用,就像要与
    let
    一起使用的自定义运算符

    (旁注:我将其命名为“collate”,但这是一个糟糕且误导性极强的名称,但没有时间命名……)

    下面是一个用法示例:

    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'}
    等等,在尽可能多地并发执行时正确分组--它所做的唯一缓冲是将特定输入的所有段匹配在一起

    这是一个正在运行的演示


    崩溃 可能有更清晰/更简单的方法来实现这一点,特别是如果您可以硬编码一些东西,而不是使它们通用。但让我们来分析一下我所做的

    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),
            {}
          )
        );
    
    constcollate=(…段)=>source$=>
    来源$
    //对于每个输入对象,我们使用索引作为ID
    //(由Rx提供为自动递增)
    .mergeMap((对象,索引)=>{
    //分段是我们应该如何进行的配置
    //将数据分块到并发处理通道中。
    //所以我们返回一个数组,mergeMap将使用它
    //好像它是一个可观察的,或者我们可以使用
    //可观察。从(arr)到更清晰
    返回segments.map({key,work})=>{
    常量输入=对象[键];
    //“work”函数将返回
    //可以观察到的东西,如
    常量输出$=工作(输入);
    返回可观察的.from(输出$).map(输出=>({
    //把我们结束的指数放在后面,我们就可以
    //将每个部分重新缝合在一起
    指数
    结果:{[键]:输出}
    }))
    })
    })
    //我已在mergeMap中返回数组
    //所以我们需要再展平一层。这是
    //相当令人困惑……可能更清晰,但#约洛
    .mergeAll()
    //现在我们有了每个片段的所有结果流
    //没有保证的顺序,所以我们需要将它们组合在一起
    .群比(
    obj=>obj.index,
    obj=>obj.result,
    //这很难解释。它被用作通知程序
    //要说何时完成组$,我们需要完成它
    //在我们收到该组的每个片段之后
    //我们跳过所有的通知,除了我们期望的最后一个
    //但请记住,这不会跳过下游的元素!
    //仅作为durationSelector通知程序的一部分
    组$=>组$.skip(segments.length-1)
    )
    .mergeMap(组$=>
    //合并返回到一个对象中的每个分段对象
    //所以它的形状和它进来时一样,但结果是什么
    组$.reduce(
    (obj,result)=>Object.assign(obj,result),
    {}
    )
    );
    


    我没有考虑或担心错误处理/传播可能如何工作,因为这在很大程度上取决于您的用例。如果您无法控制每个段的处理,那么还包括某种类型的超时和
    。建议采取(1)
    ,否则您可能会泄露订阅。

    我想您可以使用
    pairwise()
    然后
    merge()
    这两条流,但我不确定您想要完成什么,因此我可以给出更准确的建议。我添加了更多信息。你不明白哪一部分?不清楚你到底需要完成什么。还不清楚你所说的平行是什么意思——它在不同的环境中对不同的人意味着不同的事情。e、 g.当您仍在等待匹配的“a”或“b”时,缓冲策略是什么?你只是同时做这对,而不是每对的倍数吗
    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');
        }
      })
    );
    
    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),
            {}
          )
        );