szydra
szydra

Reputation: 154

Should I use shareReplay as the last operator?

I develop an Angular 9 application and I don't understand how to use shareReplay operator together with other operators. I did something like the following:

if (!this.cache[key]) {
  this.cache[key] = this.http.get(...).pipe(
    shareReplay(1),
    flatMap(...),
    map(...),
    reduce(...)
  );
}
return this.cache[key];

After this my application stucked with 100% CPU usage. When I changed it to:

if (!this.cache[key]) {
  this.cache[key] = this.http.get(...).pipe(
    flatMap(...),
    map(...),
    reduce(...),
    shareReplay(1)
  );
}
return this.cache[key];

it seems to work fine. Is it necessary to use the shareReplay operator as the last one? Where this high CPU usage comes from?

Edit: a more detailed code snippet:

this.http.get(...).pipe(
  // I would like to avoid several same http calls
  shareReplay(1),
  // I would like to flatten an array of objects that comes from backend
  flatMap(option => option),
  // I need to map all objects to a format that is acceptable by some library
  map(option => ({
    value: option.key,
    label: option.value
  })),
  // I must reduce it back to an array of the new objects
  reduce((acc: { value: string; label: string }[], option) => {
    acc.push(option);
    return acc;
  }, [])
);

Upvotes: 3

Views: 1328

Answers (1)

ggradnig
ggradnig

Reputation: 14199

The position of the share operator in the pipe is important, because it determines how many subscribers will subscribe to operators that come before share.

Take the following example:

const interval$ = interval(1000).pipe(
  tap(() => console.log("Interval Triggered")
);

interval$.subscribe();
interval$.subscribe();

What you'll see is that we get two console message every second. That makes sense, because in RxJS, every subscriber ("consumer") will cause the factory to create a new "producer" (here a setInterval). In this case - there is no share operator - every subscriber will go up the operator chain and subscribe to the previous operator - until we subscribe to interval twice.

Now, let's take a look at share:

const interval$ = interval(1000).pipe(
  tap(() => console.log("Interval Triggered"),
  share()
);

interval$.subscribe();
interval$.subscribe();

With this version, we'll only get one message per second. Why?

Because share won't subscribe to it's source as long as there is one active subscriber. So, we subscribe to share() twice, but share() only subscribes to interval once.

If we switch positions, we get a different result:

const interval$ = interval(1000).pipe(
  share(),
  tap(() => console.log("Interval Triggered"),
);

interval$.subscribe();
interval$.subscribe();

In this case, we subscribe to tap twice - and those two tap operators subscribe to share twice. That's why we get two console messages per second.

As a rule of thumb, you'll most likely want your share at the end of your pipe, as this will cause the operators that come before share to be subscribed to only once.


To explain your CPU consumption: I suspect that you subscribe very often to this Observable - with share not being the last operator, this means that you'll resubscribe to flatMap etc. over and over again.

Upvotes: 7

Related Questions