简体   繁体   English

仅针对 Observable 的某些发射执行 rxjs 管道的一部分

[英]Do parts of an rxjs pipe only for certain emissions of the Observable

I have an rxjs-Observable which I subscribe to.我有一个我订阅的 rxjs-Observable。 I now have two different necessities to handle within the pipe of the Observable.我现在有两种不同的需要在 Observable 的管道内处理。

First I want to map the output.首先我想映射输出。 Second I want to use tap to trigger a side-effect, but the side-effect must not be triggered on the first emission.其次,我想使用tap来触发副作用,但不能在第一次发射时触发副作用。

So this obviously does not work, because the skip is working globally on the pipe:所以这显然不起作用,因为skip在管道上全局工作:

this.userChangeSubscription = this.userStateService.userState$
  .pipe(
    map(userState => userState.prop),
    skip(1),
    tap(() => this.sideEffect())
   )
  .subscribe();

Is there any way to do this, without subscribing to the Observable twice?有没有办法做到这一点,而无需订阅 Observable 两次?

edit : Ok, I now have several options that all seem to be working.编辑:好的,我现在有几个似乎都有效的选项。 Now: which one to choose?现在:选择哪一个?

I've seen that there are already working answers, but I think your best bet would be to write your own rxjs operator for this usecase.我已经看到已经有有效的答案,但我认为最好的办法是为此用例编写自己的 rxjs 运算符。 This results in a clean solution and inside your pipe function clarifies your intent.这会产生一个干净的解决方案,并且在您的pipe函数中阐明了您的意图。

To do so we need to make use of thedefer observable :为此,我们需要使用defer observable

function tapSkipFirst<T>(fn: Function): OperatorFunction<T, T> {
  return function(source: Observable<any>) {
    return defer(() => {
      let skip = true;
      return source.pipe(
        tap((v?:any) => {
          if (!skip) {
            fn(v);
          }
          skip = false;
        })
      );
    });
  };
}

We are using the skip variable to decide whether we run the side effect function or not.我们使用skip变量来决定是否运行副作用函数。 At the end of the first run we toggle skip to be false and therefore run the side effect function for all following runs.在第一次运行结束时,我们将skip切换为 false,因此为所有后续运行运行副作用函数。 We need to use the defer observable here, because the code inside defer only gets called on subscription and not on creation.我们需要在这里使用defer observable,因为defer的代码只在订阅时调用,而不是在创建时调用。 This is important because otherwise all subscriptions would share the same skip variable.这很重要,否则所有订阅都将共享相同的skip变量。

And now you can easily use the new custom operator like:现在您可以轻松使用新的自定义运算符,例如:

this.userChangeSubscription = this.userStateService.userState$
  .pipe(
    map(userState => userState.prop),
    tapSkipFirst(() => this.sideEffect())
   )
  .subscribe();

Yes it is possible.对的,这是可能的。 Example:例子:

const userProp$ = this.userStateService.userState$.pipe(
  map(userState => userState.prop),
  shareReplay({ bufferSize: 1, refCount: true })
);

Here shareReplay will let you share that resource between multiple subscribers without triggering the whole chain again.在这里 shareReplay 将让您在多个订阅者之间共享该资源,而无需再次触发整个链。 Once all the subscribers are done listening, the shareReplay will end and the observable above will be ended.一旦所有订阅者都收听完毕,shareReplay 将结束,上面的 observable 也将结束。

Then you can subscribe as many times as you want and also isolate the side effect which is a good idea in the first place:然后,您可以根据需要多次订阅,并隔离副作用,这首先是一个好主意:

userProp$
  .pipe(
    skip(1),
    tap(() => this.sideEffect())
  )
  .subscribe();

You can use the switchMap operator for such use case.您可以将switchMap运算符用于此类用例。 It gives you an index parameter allowing you to perform different actions based on the emission being the first, second, third etc..它为您提供了一个index参数,允许您根据第一个、第二个、第三个等发射来执行不同的操作。

In this case, the sideEffect will not get triggered if the index equals 0 (the first emission) but will get triggered for any other futures emissions.在这种情况下,如果index等于 0(第一次排放),则sideEffect不会被触发,但会因任何其他期货排放而被触发。

I use this operator for triggering side effects or altering the value being emitted based on a condition.我使用此运算符来触发副作用或根据条件更改发出的值。 This is great because you don't need to split the source Observable into two different Observables having a different pipeline.这很棒,因为您不需要将源 Observable 拆分为具有不同管道的两个不同 Observable。

this.userChangeSubscription = this.userStateService.userState$
  .pipe(
    map(userState => userState.prop),
    switchMap((value, index) => {
      return index > 0
        ? of(value).pipe(tap(() => this.sideEffect()))
        : of(value);
    })
  )
.subscribe();

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM