rxjs

Execute observables sequentially, use result from first observable to call http endpoint


I have an initial 2 http requests I need to make. The first will respond with an array of numbers. Only after the first request can I make the second http request which returns void. After the second http request, I want to then use the values from the first http request and iterate through them making additional http requests for each item

How can I achieve this?

What I have tried

Attempt 1

obs1$ = of([1, 2, 3]);
obs2$ = of(void 0);
    
concat(obs1$, obs2$).subscribe({next: data => {}});

but data here was of type number[] | undefined, I am only interested in number[]

Attempt 2

obs1$ = of([1, 2, 3]);
obs2$ = of(void 0);
    
obs1$.pipe(
      first(),
      tap(() =>
        obs2$
      )
    )
    .subscribe({
      next: data => {
        data.forEach(item => 
          //http POST with item
        );
      }
    });

It's not the typical use of tap which is more of side effects, setting a value. All the additional http calls in the subscription doesn't look great and makes error handling tricky


Solution

  • We can use switchMap to switch between the observables in sequence. There are two ways to do this.

    In Parallel (Inner API call made with the returned array):

    Here we use switchMap, to sequentially execute obs1 followed by obs2, then another switchMap, to call the inner observables based on the returned array. We can use forkJoin to make the API calls happen in parallel. Finally we get the returned observables in the subscribe.

    import './style.css';
    
    import { rx, of, map, switchMap, forkJoin } from 'rxjs';
    
    const obs1$ = of([1, 2, 3]);
    const obs2$ = of(void 0);
    const fakeApi = (num: number) => of(`${num} - api call done`);
    
    obs1$
      .pipe(
        switchMap((arrayOfNum: Aray<number>) => {
          return obs2$.pipe(
            switchMap(() => {
              const apiCallArr$ = arrayOfNum.map((num: number) => fakeApi(num));
              return forkJoin(apiCallArr$);
            })
          );
        })
      )
      .subscribe({
        next: (response) => {
          console.log(response);
        },
        error: () => console.log('some error occoured'),
      });
    

    Stackblitz Demo

    In Sequence (Inner API call made with the returned array):

    Here its the same explanation as above, but we use from to convert the array, to a stream of observables (only by one emitted). Then we use concatMap to perform API calls on the stream (also preserving the order of the emits). Finally we use toArray to collect the values and emit, once the array is completed fully.

    import './style.css';
    
    import { rx, of, map, from } from 'rxjs';
    import { toArray, switchMap, concatMap } from 'rxjs/operators';
    
    const obs1$ = of([1, 2, 3]);
    const obs2$ = of(void 0);
    const fakeApi = (num: number) => of(`${num} - api call done`);
    
    obs1$
      .pipe(
        switchMap((arrayOfNum: Aray<number>) => {
          return obs2$.pipe(
            switchMap(() => {
              return from(arrayOfNum).pipe(
                concatMap((data: any) => fakeApi(data)),
                toArray()
              );
            })
          );
        })
      )
      .subscribe({
        next: (response) => {
          console.log(response);
        },
        error: () => console.log('some error occoured'),
      });
    

    Stackblitz Demo