0

I have a use case where I need to make two async calls. order is important here. The first call needs to complete (or fail) before starting the next call.

I have a example code below showing what I need but it is not done in a reactive way (ie. using rxjs operators)

public performDBTasks(): Observable<void> {
    return Observable.create(
    (observer)=> {
        this.performDBTask1().subscribe(
        ()=> {
            this.performDBTask2().subscribe(
            ()=> {
                observer.next();
                observer.complete();
            },
            ()=> {
                observer.error();
            });
        },
        (error)=> {
            this.performDBTask2().subscribe(
            ()=> {
                observer.next();
                observer.complete();
            },
            ()=> {
                observer.error();
            });         
        }); 
    });
}

Update: just to clarify the two calls are http like calls so they will return data & complete or error. There will be no stream of data.

declan roche
  • 79
  • 2
  • 8

3 Answers3

1

You could use from with concatMap, where it will wait until the previous request has finished.

const { from, concatMap, fetch: { fromFetch }, tap } = rxjs;

const endpoints = [
  // Returns 404
  'https://dummy.restapiexample.com/api/v1/404-error',
  // Returns 200 
  'https://dummy.restapiexample.com/api/v1/employee/1'
];

from(endpoints).pipe(
  // Won't fetch until the previous request has finished.
  concatMap(url => fromFetch(url).pipe(
    tap(url => console.log(`Making request to: ${url.url}`)),
  )),
  tap(resp => console.log(`Code: ${resp.status}`))
).subscribe()
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/7.8.0/rxjs.umd.min.js"></script>
Get Off My Lawn
  • 34,175
  • 38
  • 176
  • 338
1

Use a switchMap and a catchError to both return performDBTask2

const { of, throwError, switchMap, tap, catchError } = rxjs;

const performDBTask1 = () =>
  Math.random() > .5 // 50/50 chance of erroring
    ? of('Success Task 1')
    : throwError('Error Task 1');

const performDBTask2 = () => of('Success Task 2');

performDBTask1().pipe(
  tap({
    next: (val) => { console.log(val); },
    error: (error) => { console.log(error); }
  }),
  catchError(() => performDBTask2()),
  switchMap(() => performDBTask2())
).subscribe((val) => {
  console.log(val);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/7.8.0/rxjs.umd.min.js"></script>

Everytime you hit the Run code snippet button above there is a 50/50 chance of performDBTask1 failing but performDBTask2 always succeeds.

Adrian Brand
  • 20,384
  • 4
  • 39
  • 60
0

You can use switchMap and last for your problem.

const { Subject, merge, of} = rxjs;
const { switchMap, last, map, catchError } = rxjs.operators;

const first$ = new Subject();
const second$ = new Subject();

const result$ = first$.pipe(
  // catch an error and return an observable that gets "?" and completes afterwards
  catchError(() => of("?")),
  // only emit if observable completes and return last value
  last(),
  // switch to observable second$
  switchMap((firstValue) =>
    second$.pipe(
      // just for logging purpose - log the first$ and second$ value
      map((secondValue) => [firstValue, secondValue])
    )
  )
);

result$.subscribe(console.log);

first$.next(1);
first$.next(2);
first$.complete();
second$.next(4);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.3/rxjs.umd.min.js"></script>

Info

  1. From your question I don't know what exact output you want to achieve. Your qeustions describes only the async call order. If the output does not fit your needs, comment and I can try to adapt. But please keep in mind to specify/udpate your question.
  2. Your sample code subscribes many subscriptions that are not handled. It is a good practice to avoid as many subscriptions as possible. In perfection you have maximum one subscription for every ui update or backend/service call. Read more in this question about how to avoid memory leaks with rxjs pipes
  3. *Creating the error via first$.error("msg") is not possible. Corresponding stackoverflow question: How do I throw an error on a behaviour subject and continue the stream. Feel free to be simulate an error on the pipe. The simulation is not needed to answer the question so I will avoid implementing a simulation.
Jonathan Stellwag
  • 3,843
  • 4
  • 25
  • 50