karoluS
karoluS

Reputation: 3218

How to get rid of multiple nested switchMap with early returns

I got 3 endpoints that returns upcoming, current, past events. I should show only the one that is the farthest in the future. To optimize the calls and not to call all the endpoints at once.I have written a simple RxJs stream where I call the first endpoint and if it does not return data I call second and so on. The code looks like this:

this.eventsService.getUpcoming(id).pipe(
      switchMap((upcoming) => {
        if (upcoming.length) {
          return of(upcoming);
        }
        return this.eventsService.getCurrent(id).pipe(
          switchMap((current) => {
            if (current.length) {
              return of(current);
            }
            return this.eventsService.getPast(id)
          })
        );
      }),
    // some other pipe operators map etc.

It is possible not to have nested switch map within a switch map?

Upvotes: 5

Views: 829

Answers (3)

martin
martin

Reputation: 96889

I think you could use just concat() to make the calls sequential and then take(1) and skipWhile() to automatically complete when the first useful response arrives:

concat(
  this.eventsService.getUpcoming(id),
  this.eventsService.getCurrent(id),
  this.eventsService.getPast(id)
).pipe(
  skipWhile(response => response.length === 0),
  defaultIfEmpty([]),
  take(1),
);

take(1) will complete the chain when the first item in skipWhile doesn't match the condition.

Upvotes: 2

Daniel Gimenez
Daniel Gimenez

Reputation: 20494

You can use concat operator to create an observable which sequentially emits values from each observable. Pipe the results to the find operator that will return the result from the first result that meets the condition and complete the observable. This will prevent subsequent observables to be executed from the stream created by concat.

Difference between first and take

One side effect of find that I think you will find useful for your example is that if no conditions are met, then the last result is still emitted. This is different then using an operator like first which will throw an error if the source observable completes without a match, or take which won't emit anything since a prior operator would be used for filtering emissions.

So in your case you'll at least get an empty array if all responses are empty.

concat(
  // try each request in order.
  this.eventsService.getUpcoming(id),
  this.eventsService.getCurrent(id),
  this.eventsService.getPast(id)
).pipe(
  // emits the first result that meets the condition.
  find(response => response.length > 0) 
);

Upvotes: 1

Owen Kelvin
Owen Kelvin

Reputation: 15083

Try something like this

this.eventsService.getUpcoming(id).pipe(
  switchMap((upcoming) => {
    if (upcoming.length) {
       return of(upcoming);
    }
    return this.eventsService.getCurrent(id)
  },
  switchMap((current) => {
     if (current.length) {
       return of(current);
     }
     return this.eventsService.getPast(id)
  })
)

This way you do not nest the switchMap

Upvotes: 2

Related Questions