5

I have to make an API call (returns Promise) after another API call (returns Observable).

The userService.getAuthUser() returns an Observable.

The userService.updateUser() returns a Promise.

I can achieve that by putting updateUser() inside getAuthUser()

this.userService.getAuthUser().subscribe((res) =>{
  this.userService.updateUser(res.id, <User>({
    name: 'Sample Name'
  })).then((res) =>{
    console.log('update user success');
  }).catch((err) => {
    console.log('update user failed');
  })
},
(err) => {
  console.log('get auth user failed');
})

But i feel that is not very good doing this way, kind of callback hell, any better way to do so?

Note: I can't change userService.updateUser() to Observable.

hades
  • 4,294
  • 9
  • 46
  • 71
  • You are dependent on the value returned by the observable for the next `updateUser()` part. So this is fine. If you want to have a cleaner code, create another method to update the user with an argument and call it inside the subscribe – dileepkumar jami Feb 16 '19 at 13:04
  • You can convert an Observable to a Promise and chain Promises though. https://stackoverflow.com/questions/36777284/angular-2-convert-observable-to-promise https://stackoverflow.com/a/54710279/495157 – JGFMK Feb 16 '19 at 13:12
  • You can also convert a Promise to an Observable https://stackoverflow.com/questions/39319279/convert-promise-to-observable/39319314 https://stackoverflow.com/questions/37771855/chaining-observables-in-rxjs – JGFMK Feb 16 '19 at 13:18

2 Answers2

9

There are a few ways you can achieve this.

One way if you want the getAuthUser stream to remain active is to transform the promise to an observable with the from operator. This will allow you to continue the stream and react to errors/handle success for the entire stream. You can also specify where in the stream to react to specific errors with the catchError operator.

Something similar to:

this.userService.getAuthUser()
  .pipe(
    catchError(err => {
      // throw an error how you like, see the 'throw' operator
      // can also create with other operators like 'of'
    }),
    switchMap(auth =>
      from( // will detect promise and return observable
        this.userService.updateUser(res.id, <User>({
          name: 'Sample Name'
        }))
      )
    ),
    catchError(err => {
      // throw an error how you like, see the 'throw' operator
      // can also create with other operators like 'of'
    })

  ).subscribe(
    (res) => {
      // if you were expecting data at this point
    }, (err) => {
      // if you threw the error
    }
  )

Another way if you don't need the stream to remain active, you can convert the Observable from the first stream to a Promise with .toPromise(). From here, you have two typical paths to follow. You can use async/await, or just chain the promises.

For the async await, something similar to:

// the method will have to be async for await to not show errors

const authUser = await this.userService.getAuthUser().toPromise();

// determine if you should update
// if so

const updateUserResult = await this.userService.updateUser(res.id, <User>({name: 'Sample Name'}));
  • for the `catchError` part it shows some error `Argument of type '(err: any) => void' is not assignable to parameter of type '(err: any, caught: Observable) => ObservableInput<{}>'. Type 'void' is not assignable to type 'ObservableInput<{}>'.`, i have to use `throw` – hades Feb 16 '19 at 13:59
  • Which catchError part (or both)? Sorry, it was a rough up to give the "stream" example. I'd be happy to update with what you've found –  Feb 16 '19 at 14:16
  • 1
    both actually, i have throw the error like `catchError(err => { console.log(err) throw err; }` else IDE is complaning – hades Feb 16 '19 at 14:19
  • Oh, gotcha, the "return an observable still" part. throw is one option, but any way you can create a new observable is valid. One use case is to catch the error, handle it there, and change the stream to a new observable so downstream effects can react differently if needed. –  Feb 16 '19 at 14:22
3
import { flatMap, map, catchError } from 'rxjs/operators';
import { of, from } from 'rxjs';

this.userService
  .getAuthUser()
  .pipe(
    flatMap(
      ({ id }) =>
        from(this.userService.updateUser(id)).pipe(
          map(response => {
            console.log('update user success');
            return response;
          }),
          catchError(error => {
            console.log('update user failed');
            return of(error);
          })
        ),
      catchError(error => {
        console.log('get auth user failed');
        return of(error);
      })
    )
  )
  .subscribe();
korteee
  • 2,640
  • 2
  • 18
  • 24