carlos
carlos

Reputation: 663

Run multiple observables sequentially and in order

I want to chain multiple observables in a single stream and preserve the previous value further down the pipe chain.

Each observable must run sequentially (one after the other, example: if the first observable completes it must go to the next one) and the order is important. I don't want to subscribes to them in parallel (just like forkJoin)

The output must give me the user info, cellphones, addresses and the email of the user.

I can do it with switchmap, this approach does work; but is there a better approach?

Just a dummy example:

    this.userService.getUser(id)
    .pipe(
        switchMap(userResponse => {
            return this.cellphoneService.getCellphones(userResponse.id)
                .pipe(
                    map(cellphonesResponse => [userResponse, cellphonesResponse])
                )
        }),
        switchMap(([userResponse, cellphonesResponse]) => {
            return this.emailService.getEmails(userResponse.id)
                .pipe(
                    map(emailResponse => [userResponse, cellphonesResponse, emailResponse])
                )
        }),
        switchMap(([userResponse, cellphonesResponse, emailResponse]) => {
            return this.addressService.getAddresses(userResponse.id)
                .pipe(
                    map(addressResponse => [userResponse, cellphonesResponse, emailResponse, addressResponse])
                )
        }),
    ).subscribe(response => console.log(response))

Upvotes: 5

Views: 7393

Answers (2)

customcommander
customcommander

Reputation: 18891

You can create an observable of observables. The concatAll operator will subscribe to each one of them in order but only one at a time. Meaning that concatAll first subscribes to name$ and emits its value. Then name$ completes and concatAll moves to email$, etc.

In the end concatAll will emit three values but reduce will accumulate these values into one array. Thus the data$ stream emits only once.

const name$ = timer(250).pipe(
  map(() => 'john doe'),
  tap(str => console.log(`received: ${str}`))
);

const email$ = timer(150).pipe(
  map(() => '[email protected]'),
  tap(str => console.log(`received: ${str}`))
);

const phone$ = timer(50).pipe(
  map(() => '777 555 666'),
  tap(str => console.log(`received: ${str}`))
);

const data$ = from([name$, email$, phone$]).pipe(
  concatAll(),
  reduce((acc, data) => acc.concat(data), [])
);

data$.subscribe(d => console.log('emitted:', d));
<script src="https://unpkg.com/[email protected]/dist/bundles/rxjs.umd.min.js"></script>
<script>
const {from, timer} = rxjs;
const {map, tap, concatAll, reduce} = rxjs.operators;
</script>

Upvotes: 1

BizzyBob
BizzyBob

Reputation: 14740

Instead of mapping your response to an array after each call, you can instead nest your switchMaps, then all responses will be in scope, so you can just use a single map:

this.userService.getUser(id).pipe(
   switchMap(user => this.cellphoneService.getCellphones(user.id).pipe(
      switchMap(cellphones => this.emailService.getEmails(user.id).pipe(
         switchMap(email => this.addressService.getAddresses(user.id).pipe(
            map(address => [user, cellphones, email, address])
         ))
      ))
   ))
).subscribe(response => console.log(response))

This is described in a little more detail in this answer

Upvotes: 3

Related Questions