ggradnig's implementation is the correct solution, however I'd like to go over a more in-depth analysis on WHY it works so there's no confusion if anyone runs into this problem in the future.
When you subscribe to an observable, most of the time you're only passing in one callback function which describes how you want to deal with data from the stream when you receive it. In reality though there are 3 different callbacks that can be included in the observer for different types of events. They are:
next - Called when data is received from the stream. So if you’re making a request to get some pokemon stats, it’s going to
call the “next” callback function and pass that data in as the
input. Most of the time this is the only data you care about and the
creators of rxjs knew this so if you only include 1 callback
function into a subscription, the subscription will default to
passing in “next” data into this callback.
error - Pretty self explanatory. If an error is thrown in your observable and not caught, it will call this callback.
- complete - Called when the observable completes.
If you wanted to deal with all the different types of data emitted from an observable, you could write an observer in your subscription that looks something like this:
this.http.get(“https://pokemon.com/stats/bulbasaur”).subscribe({
next: () => { /* deal with pokemon data here */},
error: () => {/* called when there are errors */},
complete: () => {/* called when observable is done */}
})
Again this is unnecessary most of the time but it’s essential to understand these types of events when we call the “.toPromise()” method on an Observable. When we convert an Observable to a Promise, what’s happening is the Promise is going to resolve with the last “next” data emitted from the Observable as soon as “Complete” method on the Observable is called. This means if the “Complete” callback isn’t called, the Promise will hang indefinitely.
Yeah, I know what you’re thinking: I convert my http requests from Observables to Promises all the time and I never run into a situation where my Promise hangs indefinitely. That’s because the angular http library calls the “Complete” callback on the Observable as soon as all the data is received from the http call. This makes sense because once you receive all the data from the request, you’re donezo. You’re not expecting any more data in the future.
This is different from this situation described in the question where you’re making a call to firestore which I know from experience uses sockets to transmit information, not http requests. This means that through the connection you might receive an initial set of data… and then more data… and then more data. It’s essentially a stream that doesn’t have a definitive end, so it never has a reason to call the “Complete” callback. Same thing will happen with Behavior and Replay subjects.
To circumvent this problem you need to force the Observable to call the “Complete” callback by either piping in “first()” or “take(1)” which will do the same thing, call the “next” callback function with the initial set of data as the input and then call the “Complete” callback.
Hope this is useful to somebody out there cus this problem confused the hell out of me for the longest time.
Also this video is a great reference if you’re still confused: https://www.youtube.com/watch?v=Tux1nhBPl_w
new Promise
call in your consumer. – Gorges