使用combineLatest将n + 1个流添加到RXJS流

时间:2017-10-20 19:30:21

标签: rxjs

希望有人可以帮我解决这个问题。 我有两个流,我需要使用运算符combineLatest。过了一会儿,我需要添加动态流,还需要使用combineLatest。 这是我需要做的:

stream a ---------a---------------b-------------c------------------------>
stream b ----1--------2-----3-------------------------4------------------>
stream c (not defined at start)      -----z-----------------x------------>
stream d (not defined at start)                         ----------k------>
                                 (combineLatest)
result   ---------(a1)(a2)--(a3)--(b3)----(b3z)-(c3z)-(c4z)-(c4x)-(c4xk)->   

更新

更具体一点我想转此STREAM (link) enter image description here

结果如下:

A----B---B0-C0--D0--D1--E1--E1a--F1a--F2a---G2a---G3a--H3a-H3b--I3b

3 个答案:

答案 0 :(得分:2)

everything is a stream的想法。甚至溪流:)



const onNew$ = new Rx.Subject();

const a$ = Rx.Observable.interval(1000).mapTo('a');
const b$ = Rx.Observable.interval(1000).mapTo('b');

const comb$ = Rx.Observable
  .merge(
    onNew$,
    Rx.Observable.from([a$, b$]),
  )
  .scan((acc, v) => {
      acc.push(v);
      return acc;
    }, [])
  .switchMap(vs => Rx.Observable.combineLatest(vs))

comb$.take(4).subscribe(v => console.log(v));

setTimeout(
  () => onNew$.next(Rx.Observable.interval(1000).mapTo('c')),
  2000,
);
setTimeout(
  () => onNew$.next(Rx.Observable.interval(1000).mapTo('d')),
  4000,
);

<script src="https://unpkg.com/rxjs/bundles/Rx.min.js"></script>
&#13;
&#13;
&#13;

答案 1 :(得分:1)

采用Oles的答案,简化一点并添加问题更新中给出的测试数据

const Subject = Rx.ReplaySubject
const ReplaySubject = Rx.ReplaySubject

const newStream =  new Subject()

// Set up output, no streams yet
const streamOfStreams = newStream
  .scan( (acc, stream) => {
    acc.push(stream);
    return acc; 
  }, [])
  .switchMap(vs => Observable.combineLatest(vs))
  .map(arrayOfValues => arrayOfValues.join(''))    // declutter
  .subscribe(console.log)

// Add a stream
const s1 = new ReplaySubject() 
newStream.next(s1)
// emit on streams
s1.next('A'); s1.next('B')

// Add a stream
const s2 = new ReplaySubject() 
newStream.next(s2)
// emit on streams
s2.next('0'); s1.next('C')
s1.next('D'); s2.next('1'); s1.next('E'); 

// Add a stream
const s3 = new ReplaySubject() 
newStream.next(s3)
// emit on streams
s3.next('a'); 
s1.next('F'); s2.next('2'); s1.next('G'); s2.next('3'); s1.next('H'); 
s3.next('b'); s1.next('I')

工作示例:CodePen

更新

Christian慷慨地提供了一些测试流,这些测试流比我上面使用的测序主题更“真实”。不幸的是,这些突出显示了解决方案中的一个错误。

作为参考,新的测试流是

const streamA = Rx.Observable.timer(0,800).map(x => String.fromCharCode(x+ 65));
const streamB = Rx.Observable.timer(0,1300).map(x => x);
const streamC = Rx.Observable.timer(1100, 2000).map(x => String.fromCharCode(x+ 97));

setTimeout(() => newStream.next(streamA), 500);
setTimeout(() => newStream.next(streamB), 2000);
setTimeout(() => newStream.next(streamC), 3000);

问题#1

第一个问题源于streamOfStreams

中的核心线
  .switchMap(vs => Observable.combineLatest(vs))

这基本上说,每当出现一个新的数组流时,将其映射到新数组的combineLatest()并切换到新的observable。但是,测试可观察量很冷,这意味着每次重新订阅都会获得完整的流。

参考:Introduction to Rx - Hot and Cold observables

  

实际上,一些可观察到的序列看起来很热   冷。令人惊讶的几个例子是Observable.Interval   和Observable.Timer

所以我们得到了 - 预期A--B--B0...
- 实际A--B--A0--B0...

显而易见的解决方案是将寒冷的观察者变热,

const asHot = (stream) => {
  const hot = stream.multicast(() => new Rx.Subject())
  hot.connect()
  return hot
}

但这省略了序列中的B0 A--B--C0...,所以我们想要热的+ 1之前的,可以使用缓冲区大小为1

const asBuffered = (stream) => {
  const bufferOne = new ReplaySubject(1)
  stream.subscribe(value => bufferOne.next(value))
  return bufferOne
}

问题#2

第二个问题来自于streamC延迟它首次发射1100ms的事实(好的测试基督徒!)。

结果是 - 预期A--B--B0--C0--D0--D1--E1--E1a...
- 实际A--B--B0--C0--D0--E1a...

这意味着我们需要延迟添加流,直到它首次发出

const addStreamOnFirstEmit = (stream) => {
  const buffered = asBuffered(stream)
  buffered.first().subscribe( _ => {
    newStream.next(buffered)
  })
}

工作示例:CodePen

有关CodePen的说明

我已经离开各种 streamAdder 函数进行实验,还有_debug版本发出流和addStream事件来显示序列。

还限制了源流,以便控制台不会滚动太多。

关于预期输出的说明

新解决方案与'G3a'之后问题中给出的预期输出不同

  • 期待A----B---B0-C0--D0--D1--E1--F1---F2---F2a---G2a---G3a--H3a--H3b--I3b
  • 实际A----B---B0-C0--D0--D1--E1--E1a--F1a--F2a---G2a---G3a--G3b--H3b--I3b

这是由于同时发射'H'和'b'。问题#3?

再试一次

为了查看解决方案是否失败,如果streamC延迟第一次发射直到两次发出streamA&amp; streamB,我把延迟改为1800ms

const streamC = Rx.Observable.timer(1800, 2000).map(x => String.fromCharCode(x+ 97));

我相信此测试的输出是正确的。

答案 2 :(得分:0)

如果您可以取消订阅并重新订阅每个新流

,则可以这样做
// Start with two streams
const s1 = new ReplaySubject(1) 
const s2 = new ReplaySubject(1) 
let out = Observable.combineLatest(s1, s2)
let subscription = out.subscribe(console.log)
s2.next('1'); s1.next('a'); s2.next('2'); s2.next('3'); s1.next('b')

// Add a new stream
subscription.unsubscribe()
const s3 = new ReplaySubject(1) 
out = Observable.combineLatest(s1, s2, s3)
subscription = out.subscribe(console.log)
s3.next('z'); s1.next('c'); s2.next('4'); s3.next('x')

// Add a new stream
subscription.unsubscribe()
const s4 = new ReplaySubject(1) 
out = Observable.combineLatest(s1, s2, s3, s4)
subscription = out.subscribe(console.log)
s4.next('k')

工作示例:CodePen