Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.0k views
in Technique[技术] by (71.8m points)

angular - Subscription to promise

In my Angular 7 application I have next function:

  getUserData(uid) {
    return this.fireStore.collection('users').doc(uid).valueChanges().subscribe(data => {
      this.writeCookie(data)
      this.currentUser = data;
    })
  }

And I want to use this function inside another method:

   someMethod() {
      ...
      new Promise(this.getUserData(uid))
         .then(() => {...})
      ...
   }

But I can't do this, because TypeScript throw an error:

Argument of type 'Subscription' is not assignable to parameter of type '(resolve: (value?: {} | PromiseLike<{}>) => void, reject: (reason?: any) => void) => void'. Type 'Subscription' provides no match for the signature '(resolve: (value?: {} | PromiseLike<{}>) => void, reject: (reason?: any) => void): void'.ts(2345)

How can I transform getUserData() method to a promise, or use forJoin instead?

Thanks in advance.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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:

  1. 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.

  2. error - Pretty self explanatory. If an error is thrown in your observable and not caught, it will call this callback.

  3. 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


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...