在RxJS中,如果可观察的源未发出任何值,如何防止运算符发出值?

时间:2019-05-23 10:26:23

标签: rxjs rxjs6

我有三个可观察物foo$bar$baz$,我将它们合并在一起以形成另一个可观察物。可以按预期运行:

  • 流以>>>
  • 开头
  • 每个值一个一个地发出
  • 流以<<<
  • 结尾

const foo$ = of('foo');
const bar$ = of('bar');
const baz$ = of('baz');

merge(foo$, bar$, baz$).pipe(startWith('>>>'), endWith('<<<')).subscribe(str => {
  console.log(str);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.2/rxjs.umd.min.js"></script>
<script>const {merge, of} = rxjs; const {startWith, endWith} = rxjs.operators;</script>

现在,如果以上三个可观察值均未发出值,则我既不想输出>>>也不会输出<<<。因此,startWithendWith仅在merge(foo$, bar$, baz$)实际发出值的情况下才能“运行”。

为了模拟我使用过滤功能拒绝了foo$bar$baz$发出的所有值。

const foo$ = of('foo').pipe(filter(() => false));
const bar$ = of('bar').pipe(filter(() => false));
const baz$ = of('baz').pipe(filter(() => false));

merge(foo$, bar$, baz$).pipe(startWith('>>>'), endWith('<<<')).subscribe(str => {
  console.log(str);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.2/rxjs.umd.min.js"></script>
<script>const {merge, of} = rxjs; const {startWith, endWith, filter} = rxjs.operators</script>

尽管您在输出中看到,startWithendWith都发出了它们的值,即使merge()没有产生任何值。

问题:如果可观察对象没有发出单个值,如何阻止startWithendWith执行?

5 个答案:

答案 0 :(得分:2)

第一个条件很简单。您可以在第一次发射之前加上concatMap

mergeMap((v, index) => index === 0 ? of('>>>', v) : of(v))

第二种情况比较棘手。您基本上希望与defaultIfEmpty相反的右边。我想不出任何简单的解决方案,因此无论如何我都可能会使用endWith,如果它是第一个也是唯一的发射,则可以忽略发射(这意味着源刚刚完成而没有发射任何东西):

endWith('<<<'),
filter((v, index) => index !== 0 || v !== '<<<'),

完整示例:

const foo$ = of('foo');//.pipe(filter(() => false));
const bar$ = of('bar');//).pipe(filter(() => false));
const baz$ = of('baz');//.pipe(filter(() => false));

merge(foo$, bar$, baz$).pipe(
  mergeMap((v, index) => index === 0 ? of('>>>', v) : of(v)),
  endWith('<<<'),
  filter((v, index) => index !== 0 || v !== '<<<'),
).subscribe(console.log);

实时演示:https://stackblitz.com/edit/rxjs-n2alkc?file=index.ts

答案 1 :(得分:1)

摘自startWith的文档:

  

返回一个Observable,它在开始发射源Observable发出的项目之前先发出您指定为参数的项目。

因此startWithendWith将始终运行。

我不知道您的预期结果应该是什么,但是如果您只想为每个发射值连接字符串,则可以使用mapswitchMap运算符。

编辑: map用来连接每个值的示例:

const foo$ = of('foo').pipe(filter(() => false));
const bar$ = of('bar').pipe(filter(() => false));
const baz$ = of('baz').pipe(filter(() => false));

merge(foo$, bar$, baz$).pipe(map(v => `>>>${v}<<<`)).subscribe(str => {
  console.log(str);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.2/rxjs.umd.min.js"></script>
<script>const {merge, of} = rxjs; const {startWith, endWith, filter, map} = rxjs.operators</script>

答案 2 :(得分:1)

您可以将合并的observable分配给变量,从流中获取第一个元素,然后在发出至少一个值时映射到要执行的observable。

const foo$ = of('foo').pipe(filter(() => false))
const bar$ = of('bar').pipe(filter(() => false))
const baz$ = of('baz').pipe(filter(() => false))

const merged$ = merge(foo$, bar$, baz$);
merged$.pipe(
  take(1),
  switchMap(() => merged$.pipe(
    startWith('>>>'),
    endWith('<<<')
  ))
).subscribe(console.log);

https://stackblitz.com/edit/rxjs-phspsc

答案 3 :(得分:0)

一种实现方法是使用 materialize-dematerialize operators 对:

source$.pipe(
  // turn all events on stream into Notifications
  materialize(),

  // wrap elements only if they are present
  switchMap((event, index) => {
    // if its first event and its a value
    if (index === 0 && event.kind === 'N') {
      const startingNotification = new Notification('N', '>>>', undefined);
      return of(startingNotification, event);
    }

    // if its a completion event and it not a first event
    if (index > 0 && event.kind === 'C') {
      const endingNotification = new Notification('N', '<<<', undefined);
      return of(endingNotification, event);
    }

    return of(event);
  }),

  // turn Notifications back to events on stream
  dematerialize()
)

Wrap source emission only if its not empty

在操场上玩这段代码:
https://thinkrx.io/gist/5a7a7f1338737e452ff6a1937b5fe05a
为了方便起见,我还在其中添加了一个空的错误源

希望这会有所帮助

答案 4 :(得分:0)

我看到所有答案都是结合运算符完成的,但是仅使用运算符来完成此解决方案有点棘手,为什么不创建自己的可观察项?我认为此解决方案是最容易理解的没有隐藏的聪明之处,没有复杂的运营商组合,没有订阅重复...

解决方案:

const foo$ = of('foo')//.pipe(filter(() => false));
const bar$ = of('bar')//.pipe(filter(() => false));
const baz$ = of('baz')//.pipe(filter(() => false));

function wrapIfOnEmit$(...obs) {
  return new Observable(observer => {
    let hasEmitted;
    const subscription = merge(...obs).subscribe((data) => {
      if (!hasEmitted) {
        observer.next('>>>');
        hasEmitted = true;
      }
      observer.next(data);
    },
    (error) => observer.error(error),
    () => {
      if (hasEmitted) observer.next('<<<');
      observer.complete();
    })
    return () => subscription.unsubscribe();
  })
}

wrapIfOnEmit$(foo$, bar$, baz$).subscribe(console.log);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.2/rxjs.umd.min.js"></script>
<script>const {merge, of, Observable} = rxjs; const {filter} = rxjs.operators</script>

希望这会有所帮助!