TL;DR: use obs.toPromise() with the Observable returned by the HttpClient library if you condiser the end-consumer will attach callbacks to the Promise before the API answers; otherwise, use .shareReplay(1) and subscribe directly a first time to the Observable.
HttpClient library, whether in NestJS or Angular, uses cold Observables.
A cold Observable will only “activate” (here: do the HTTP call) when it is subscribed to, for the simple reason that when it emits (here: when we receive response from the call), we want at least someone to listen, so that the result don’t go unheard straight to the void.
Yet, I find that in a lot of usecases involving API calls, the user which want to make the call will or will not deal with the result, depending on the situation (it could be updating a stock, which returns the resulting inventory, which we want to store somewhere or not). I’m talking about a situation where you’re developping a module to place calls, used by consumers services.
consumer <-> HTTP module <-> remote API
My first trial involved subscribing to the Observable directly in my HTTP module, before handing the Observable to the consumer.
const obs = httpClient.get(url);
obs.subscribe( () => {}, () => {} );
return obs;
Note that I provide two empty callbacks in .subscribe to avoid nuclear mole bomb (see previous post) ?
This works, but it works too much: if you observe API calls in the Network tab of your dev tools, you’ll see 2 API calls at once. Why that? Because the way it’s done, the cold Observable given by HttpClient will activate n times if you subscribe n times to it. So here, it’s fine if the consumer does not subscribe it, but possibly problematic if it does.
For that matter, there’s a nice Rxjs operator: .share(). (Btw, don’t let people who say it’s equivalent to .publish().refCount(): it’s not.)
const obs = httpClient.get(url).pipe( share() );
obs.subscribe( () => {}, () => {} );
return obs;
This operator will share the result of one call with all subscribers at the time of result coming: nice! But actually not enough. If the call is real fast, and the consumer takes time to subscribe, it will miss the first result and another call will be made (resulting again in two calls, which we really want to avoid).
So there’s another Rxjs operator for that: .shareReplay(1).
const obs = httpClient.get(url).pipe( shareReplay(1) );
obs.subscribe( () => {}, () => {} );
return obs;
This operator will share the last result it got (and HttpClient will only ever emit one result, so we’re fine with that) with any present or future subscribers. Awesome !
Now all that seems a bit far-fetched. There’s a way simpler solution, but note that it’s not appropriate if you think the consumer will attach callbacks later on, after the API has sent results (in which case, error-handling seems to be simply impossible). It’s to use the Observable .toPromise() function. Why that? Because due to the nature of the 2 structures (Observable and Promise), .toPromise() has to subscribe to the Observable, activating it in the same time, so we don’t have to take care of forcing the call anymore. Then, you handle a Promise to the consumer. This Promise will receive the first emission of the Observable (and there will be maximum one, so that’s fine), and keep it to show it to any callback the consumer will attach to .then().
const obs = httpClient.get(url);
return obs.toPromise();
Only caveat: if the API call gets into an error, and if the consumer does not handle error on the Promise before that, then you’re doomed, and the error is likely to bubble up and crash the process. At the time of writing, I found no way to handle this situation, except by interecepting the error with the Observable .catchError() operator and providing a default replacement value, which is probably not the best on the consumer side…
