【问题标题】:Is-there an opposite of the `race` operator in RxJS?RxJS 中的 `race` 运算符是否有相反的含义?
【发布时间】:2017-04-24 15:14:30
【问题描述】:

我有两个可观察对象,我想听最后一个发出第一个值的对象,是否有此操作符?类似的东西:

let obs1 = Rx.Observable.timer(500,500);
let obs2 = Rx.Observable.timer(1000,1000); // I want the values from this one
let sloth = Rx.Observable.sloth(obs1,obs2);

sloth observable 将发出来自obs2 的值,因为它是最后发出第一个值的那个。

如果不是这样,有没有其他办法?

【问题讨论】:

    标签: javascript rxjs rxjs5


    【解决方案1】:

    我现在看到了这种可能性,但我很好奇是否有人发现了其他东西:

    let obs1 = Rx.Observable.timer(500,500).map(i=>`cheetah ${i}`);
    let obs2 = Rx.Observable.timer(1000,1000).map(i=>`sloth ${i}`);
    let sloth = Rx.Observable.merge(
      obs1.take(1).mapTo(obs1),
      obs2.take(1).mapTo(obs2)
    ).takeLast(1).mergeAll()
    
    sloth.subscribe(data=>console.log(data))
    <script src="https://unpkg.com/@reactivex/rxjs@5.3.0/dist/global/Rx.js"></script>

    编辑正如@user3743222(非常好的昵称:-D)所指出的,它不适用于热门的可观察对象,但这应该没问题:

    let obs1 = Rx.Observable.timer(500,500).map(i=>`cheetah ${i}`).publish();
    let obs2 = Rx.Observable.timer(1000,1000).map(i=>`sloth ${i}`).publish();
    obs1.connect();
    obs2.connect();
    let sloth = Rx.Observable.merge(
      obs1.take(1).map((val)=>obs1.startWith(val)),
      obs2.take(1).map((val)=>obs2.startWith(val))
    ).takeLast(1).mergeAll();
        
    sloth.subscribe(data=>console.log(data));
    <script src="https://unpkg.com/@reactivex/rxjs@5.3.0/dist/global/Rx.js"></script>

    【讨论】:

      【解决方案2】:

      我喜欢你的解决方案(虽然我怀疑如果你有一个热流,你可能永远看不到第一个发出的值 - 如果源是冷的,一切看起来都很好)。你可以发jsfiddle 来检查一下吗?如果您不错过任何价值,那么您的解决方案就是最好的。如果你这样做了,可以通过将跳过的值添加回源来更正它 (obs1.take(1).map(val => obs1.startWith(val)).

      否则,对于一个通用的冗长解决方案,这里的关键是你有状态,所以你还需要scan 运算符。我们用索引标记源,并保持一个状态,该状态表示已经开始的源的索引。当除了一个之外的所有都启动时,我们知道没有启动的索引,我们只从那个索引中选择值。请注意,这应该独立于源是热还是冷,因为所有都是一次性完成的,即没有多个订阅。

      Rx.Observable.merge(
        obs1.map(val => {val, sourceId: 1})
        obs2.map(val => {val, sourceId: 2})
        obsn.map(val => {val, sourceId: n})
      ).scan( 
      (acc, valueStruct) => {
        acc.valueStruct = valueStruct
        acc.alreadyEmitted[valueStruct.sourceId - 1] = true
        if (acc.alreadyEmitted.filter(Boolean).length === n - 1) {
          acc.lastSourceId = 1 + acc.alreadyEmitted.findIndex(element => element === false)
        }
        return acc
      }, {alreadyEmitted : new Array(n).fill(false), lastSourceId : 0, valueStruct: null}
      )
      .map (acc => acc.valueStruct.sourceId === acc.lastSourceId ? acc.valueStruct.val : null )
      .filter(Boolean)
      

      也许有更短的,我不知道。我会试着把它放在一个小提琴中,看看它是否真的有效,或者如果你这样做之前让我知道。

      【讨论】:

      • 没有小提琴,因为我只是出于好奇才知道这个,但你的回答似乎很有趣。我明天试试。感谢您花时间回答。
      • 我想我会坚持我的解决方案,并使用您为热流建议的修改,因为它不那么冗长。我试图从您的解决方案中创建一个 sn-p,但存在语法错误,我不想深入研究它。无论如何,感谢您的宝贵时间。
      【解决方案3】:

      这个怎么样?

      let obs1 = Rx.Observable.timer(500,500);
      let obs2 = Rx.Observable.timer(1000,1000);
      
      let sloth = Rx.Observable.race(
          obs1.take(1).concat(obs2),
          obs2.take(1).concat(obs1)
      ).skip(1);
      

      作为一个支持多个 Observable 的函数:

      let sloth = (...observables) => 
          observables.length === 1 ?
              observables[0] :
          observables.length === 2 ?
              Rx.Observable.race(
                  observables[0].take(1).concat(observables[1]),
                  observables[1].take(1).concat(observables[0])
              ).skip(1) :
          observables.reduce((prev, current) => sloth(prev, current))[0];
      

      【讨论】:

      • 如果有两个以上的 observable 你会怎么做?
      • 当然,好问题!多个可观察对象将是这样的: let sloth = (...observables) => observables.length === 1 ? observables[0] : observables.length === 2 ? Rx.Observable.race( observables[0].take(1).concat(observables[1]), observables[1].take(1).concat(observables[0]) ).skip(1) : observables。 reduce((result, current) => sloth(result, current), Observable.of('立即项被忽略') )[0];
      【解决方案4】:

      我遇到了同样的问题,并且能够使用 mergeskipUntil 的组合来解决它。如果两个结果同时完成,pipe(last()) 会阻止您接收多个结果。

      尝试将以下内容粘贴到https://rxviz.com/

      const { timer, merge } = Rx;
      const { mapTo, skipUntil, last } = RxOperators;
      
      let obs1 = timer(500).pipe(mapTo('1'));
      let obs2 = timer(1000).pipe(mapTo('2')); // I want the values from this one
      let sloth = merge(
          obs1.pipe(skipUntil(obs2)),
          obs2.pipe(skipUntil(obs1))
      ).pipe(last())
      
      sloth
      

      【讨论】:

        【解决方案5】:

        使用 RxJS 6 和 ReplaySubject:

        function lastOf(...observables) {
          const replayable = observables
            .map(o => {
              let r = o.pipe(multicast(new ReplaySubject(1)));
              r.connect();
              return r;
            });
          const racing = replayable
            .map((v, i) => v.pipe(
              take(1),
              mapTo(i),
            ))
            ;
          return of(...racing).pipe(
            mergeAll(),
            reduce((_, val) => val),
            switchMap(i => replayable[i]),
          );
        }
        

        用途:

        const fast = interval(500);
        const medium = interval(1000);
        const slow = interval(2000);
        
        lastOf(fast, slow, medium).subscribe(console.log);
        

        【讨论】:

          猜你喜欢
          • 2018-12-04
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-04-02
          • 2018-08-19
          • 2015-10-29
          • 1970-01-01
          • 2013-04-30
          相关资源
          最近更新 更多