rxjs 多个可观察开关

时间:2021-06-04 04:10:21

标签: typescript rxjs

我有 2 个 observables,我想实现以下目标:

Observable1 获取一个值,然后忽略 Observable1 并只等待来自 Observable2 的值然后同样,从 Observable1 获取一个值em>Observable1 再次等等..

有没有办法在 rxjs 中实现这一点?行动决策树没有用。我也玩过 switchMap() 但这只能做第一部分

7 个答案:

答案 0 :(得分:2)

对此可能有更优雅的解决方案,但我通常会使用 scan 并创建一个 little state machine

import { merge, Subject } from 'rxjs';
import { scan, filter, map, take } from 'rxjs/operators';

const left = new Subject<number>();
const right = new Subject<number>();
const example = merge(
  left.pipe(map(val => ({ tag: 'left', val }))),
  right.pipe(map(val => ({ tag: 'right', val })))
).pipe(
  scan<{ tag: string; val: number }, { previous: string; emit?: number }>(
    (acc, { tag, val }) => {
      if (acc.previous !== tag) {
        return { previous: tag, emit: val };
      }
      return { ...acc, emit: null };
    },
    {
      previous: 'right',
      emit: null
    }
  ),
  filter(x => x.emit !== null),
  map(x => x.emit)
);
console.clear();
const subscribe = example
  .pipe(take(10))
  .subscribe(val => console.log('output', val));

right.next(1);
left.next(2);
right.next(3);
left.next(4);
left.next(5);
right.next(6);

答案 1 :(得分:2)

似乎有很多方法可以解决这个问题。这是另一个带有 expand 的。

import { interval } from 'rxjs';
import { expand, mapTo, take } from 'rxjs/operators';

const left = interval(1000).pipe(mapTo('left'));
const right = interval(5000).pipe(mapTo('right'));
const example = left.pipe(
  take(1),
  expand(x => (x === 'left' ? right : left).pipe(take(1)))
);
const subscribe = example.subscribe(val => console.log(val));

如果有人有办法使用 buffer* 运算符之一来做到这一点,我会很感兴趣

答案 2 :(得分:2)

我为您创建了一个自定义运算符 (toggleEmit),它接受 N 个可观察对象并在它们之间切换。

const result$ = toggleEmit(source1$, source2$);

已实现的功能:

  • N observables 可以提供给 toggleEmit 操作符
  • Observable 将被一一发出并开始重复。方向是从索引 0 到索引 N.length。当最后一个 observable 发出时,它再次从 0 开始
  • Observable 一次只能发出一次:0 - N.length。多次发射是 distincted

仅供参考:代码实际上非常简单。它看起来很大,因为我添加了一些注释以避免混淆。如果你有问题评论,我会尽量回答。

const { Subject, merge } = rxjs;
const { map, scan, distinctUntilChanged, filter } = rxjs.operators;

const source1$ = new Subject();
const source2$ = new Subject();

function toggleEmit(...observables) {
  // amount of all observables
  const amount = observables.length;
  
  // create your updating state that contains the last index and value
  const createState = (value, index) => ({ index, value });

  /*
  * This function updates your state at every emit
  * Keep in mind that updateState contains 3 functions:
  * 1. is called directly: updateState(index, amount)
  * 2. is called by the map operator: map(val => updateState(index, amount)(val)) -> Its just shorthand written
  * 3. is called by the scan operator: fn(state)
  */
  const updateState = (index, amount) => update => state =>
    // Check initial object for being empty and index 0
    Object.keys(state).length == 0 && index == 0
    // Check if new index is one higher
    || index == state.index + 1
    // Check if new index is at 0 and last was at end of observables
    || state.index == amount - 1 && index == 0
      ? createState(update, index)
      : state
  
  // Function is used to avoid same index emit twice
  const noDoubleEmit = (prev, curr) => prev.index == curr.index

  return merge(
    ...observables.map((observable, index) =>
      observable.pipe(map(updateState(index, amount)))
    )
  ).pipe(
    scan((state, fn) => fn(state), {}),
    filter(state => Object.keys(state).length != 0),
    distinctUntilChanged(noDoubleEmit),
    map(state => state.value),
  );
}

const result$ = toggleEmit(source1$, source2$);

result$.subscribe(console.log);

source2$.next(0);
source1$.next(1);
source2$.next(2);
source2$.next(3);
source1$.next(4);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.3/rxjs.umd.min.js"></script>

答案 3 :(得分:1)

因为是周五,我写了一个实现。

这是javascript而不是打字稿,但你可以看到我是如何解决它的。

function roundRobinMerge(...observables) {
  const indexedObservables = observables.map(
    (obs, index) => {
        return obs.pipe(
        map(v => {
          return {
            index: index,
            value: v
          };
        })
      );
    }
  );
  let nextIndexToListenTo = 0;
  return merge(...indexedObservables)
    .pipe(
        filter(n => n.index === nextIndexToListenTo),
      tap(n => nextIndexToListenTo = (n.index + 1)%indexedObservables.length),
      map(n => n.value)
    );
}

要查看实际效果,请转到 https://rxviz.com,然后复制并粘贴下面的代码

const { interval, merge } = Rx;
const { take, map, filter, tap, share } = RxOperators;

function roundRobinMerge(...observables) {
  const indexedObservables = observables.map(
    (obs, index) => {
        return obs.pipe(
        map(v => {
          return {
            index: index,
            value: v
          };
        })
      );
    }
  );
  let nextIndexToListenTo = 0;
  return merge(...indexedObservables)
    .pipe(
        filter(n => n.index === nextIndexToListenTo),
      tap(n => nextIndexToListenTo = (n.index + 1)%indexedObservables.length),
      map(n => n.value)
    );
}

// Now to demonstrate it
function mapper(label) {
  let index = 0;
  return () => `${label}${++index}`;
};

const a = interval(750)
    .pipe(
        map(mapper('a')),
        take(10),
      share()
   );

const b = interval(1300)
   .pipe(
     map(mapper('b')),
     take(10),
     share()
   );


merge(
  [
    a,
    b,
    roundRobinMerge(a, b)
  ]
);



答案 4 :(得分:1)

您可能可以通过使用 Subjects 来实现您想要的效果。

这是一个可能的解决方案。

您从创建某种开关的概念开始,该开关在 Observable1Observable2 通知时随时触发。一旦 switch 发出,然后我们 switchMap 到源可观察对象之一,即 Observable1Observable2),我们等待它通知,我们 {{ 1}} 到另一个,我们再次等待它通知,最后我们switchMap 开关重新开始。

看代码大概逻辑更清晰了。

next

Here a stackblitz 使用此解决方案

答案 5 :(得分:1)

您可以将源放在一个数组中,然后将 switchMapBehaviorSubject 一起使用:

const sources = [a$, b$];
const source$ = new BehaviorSubject<number>(0);

const emit$ = source$.pipe(
  switchMap(n => sources[n]),
  map((val, i) => {
    const nextIndex = (i+1) % sources.length;
    source$.next(nextIndex);
    return val;
  })
);

细分:

  • source$ 发出您要收听的源的索引
  • switchMap 订阅源并发出其值
  • map 调用 source$.next() 发出下一个索引,然后简单地返回接收到的值

请注意,这适用于 1 个或多个来源。

这是一个有效的 StackBlitz 演示。

答案 6 :(得分:0)

这是另一个使用 repeat 的有趣示例(无论如何在我看来)。从一个 observable 中获取一个值,然后从第二个 observable 中获取另一个值,然后重复:

import { concat, interval, of } from 'rxjs';
import { take, switchMap, repeat, mapTo } from 'rxjs/operators';

const left = interval(10000).pipe(mapTo('left'));
const right = interval(1000).pipe(mapTo('right'));

const example = left.pipe(
  take(1),
  switchMap(leftVal => concat(of(leftVal), right.pipe(take(1)))),
  repeat()
);
console.clear();
const subscribe = example.subscribe(val => console.log('output', val));

stackblitz

相关问题