2

I have the following requirement:

  • execute HTTP call A
  • wait for A to finish
  • execute HTTP call B
  • do not wait for B to finish
  • return result of HTTP call A

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?

fikkatra
  • 5,605
  • 4
  • 40
  • 66
  • 1
    I think your approach will work fine (and the only way to do it). – Harun Yilmaz May 10 '22 at 08:04
  • 2
    I believe your approach is the most readable one. I would just remove `take(1)` as there will be one event anyway. however if you want to hide subscribe, you can create your own custom operator, which will be handling this logic – Andrei May 10 '22 at 08:09

2 Answers2

2

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)
  );
}
ruth
  • 29,535
  • 4
  • 30
  • 57
  • 2
    these 2 requests start simultaniously, not as question author wanted. also I bet there should be a possibility to pass result of a first call to the second one – Andrei May 10 '22 at 08:24
  • @Andrei: Yes I just noticed it. The answer doesn't fulfill the condition _"wait for A to finish"_. It starts both observables simultaneously. – ruth May 10 '22 at 08:26
-1

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.

DFSFOT
  • 536
  • 3
  • 19
  • This mixes two styles, Promises and Observables, and exposes an async method outside of the class, which propagates Promises even more throughout the code. Yes, technically it works, but it's definitely not a 'proper Rxjs way' to solve it, like the OP asked for. – fikkatra May 10 '22 at 10:12
  • @fikkatra the public async part I get, but it's not a mix of two styles, or rather not a bad mix of two styles. Check the firstValueFrom link that I put up. It's from Rxjs docs and does the same thing in the example. I don't see how it "not a proper Rxjs way". – DFSFOT May 10 '22 at 10:22