fikkatra
fikkatra

Reputation: 5822

How can I execute 2 http calls after each other, but do not wait for the second one to complete, using rxjs?

I have the following requirement:

This seemed like a good case for the Rxjs tap operator, since call B is considered a 'side effect'. This is the code (subscribing to the result of this function is done elsewhere in the code):

public call(): Observable<any> {
  return this.http.get('/operation/a').pipe(
    tap(() => {
      this.http.get('/operation/b');
    })
  );
}

I noticed call B is not getting executed, because no one is subscribing to it. The following code fixes the problem:

public call(): Observable<any> {
  return this.http.get('/operation/a').pipe(
    tap(() => {
      this.http.get('/operation/b').subscribe();
    })
  );
}

However, this feels icky, since we now have 2 subscriptions: one inside the tap, the other one when calling this method. Technically it is not a problem, since the observable within the tap will complete and therefore it doesn't need to be unsubscribed, but if feels off.

I do not know how else to implement a 'fire and forget' without waiting for the results of call B. Is there a 'proper' way to do so with Rxjs?

Upvotes: 2

Views: 1211

Answers (2)

DFSFOT
DFSFOT

Reputation: 542

Just make the call async, wait for a but subscribe to b then return a. The call to b will be async as you're not awaiting it. I find pipes to be confusing and avoid using them unless I need to.

import { firstValueFrom } from 'rxjs';

public async call(): Something {
  let a = await firstValueFrom(this.http.get('/operation/a'));
  this.http.get('/operation/b').subscribe((b) => { ... });
  return a;
}

Also it's better to use firstValueFrom instead of .pipe(take(1)), but it's not even necessary in this case.

Upvotes: -1

Barremian
Barremian

Reputation: 31125

I don't see anything wrong with the implementation actually. Also since Angular HTTP calls only emit single value, the take(1) is redundant. If you're worried about hanging subscriptions, you could explicitly close them (eg. in the ngOnDestroy() hook). See here for more info.

However if you wish to have only a single subscription, you could use the combineLatest with a startWith piped to the 2nd observable. Without startWith(null) the subscriber wouldn't receive the emission from '/operation/a' until '/operation/b' has emitted.

Then you could use map operator to ignore the 2nd observable's emission.

Note: I'm sure there could be better solutions to this problem. The following is the quickest I could come up with

import { combineLatest, startWith } from 'rxjs';
import { map } from 'rxjs';

public call(): Observable<any> {
  return combineLatest({
    a: this.http.get('/operation/a'),
    b: this.http.get('/operation/b').pipe(startWith(null))
  }).pipe(
    map(({a, b}) => a)
  );
}

Upvotes: 2

Related Questions