Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 545
karoluS Avatar asked May 04 '21 14:05

karoluS


3 Answers

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.

like image 63
martin Avatar answered Oct 20 '22 22:10

martin


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

like image 28
Owen Kelvin Avatar answered Oct 20 '22 21:10

Owen Kelvin


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) 
);
like image 1
Daniel Gimenez Avatar answered Oct 20 '22 22:10

Daniel Gimenez