2

I have a service with some methods, most of them require a certain callback to be completed before it can do its stuff. With Promises, in pseudo, it is very easy to do this:

ready = http.get(stuff); // Returns a promise, resolves after a while

methodOne() { // methods sometimes called before promise resolves
    this.ready.then(_ => { 
        // doStuff
    });
}

methodTwo() {
    return this.ready.then(d => {
        // doOtherStuff
    });
}

Basically I need to do the stuff, only when i'm sure the service is ready. I actually only need to check if it's ready (what methodOne is doing, just illustrating with methodTwo, that it's easy to more stuff as well).

I want to try and go all in on Observables, but for this specific case, I find it really hard to compete with a similar solution for Observables.

Promises will remember the value and know if it got resolved. An Observable is somewhat more complex and it seems that creating this same flow is troublesome. I need whatever is subscribing to the Observable, to known when it's ready. Some times the method is called early - before the Observable emits and sometimes late, after the Observable already emitted.

I have this right now, but it doesn't seem to work:

this.ready$ = someObservable // Will fire after a litle while but never finish - i only need the first to check though.
  .publishReplay(1).refCount(); // Trying to replay if subscription comes after emit.

this.ready$.subscribe(_ => {
    // This will be called
});

methodOne() { 
    this.ready$.subscribe(_ => {
        // Not called
    });
};

Perhaps i misunderstood the use of publishReplay and refCount?

Per Hornshøj-Schierbeck
  • 15,097
  • 21
  • 80
  • 101
  • 1
    *"Since I'm working on an Angular project, the preferred way is to go all in on Observables"* Preferred by whom? With what justification? And what supporting data? – T.J. Crowder Aug 02 '17 at 16:47
  • @T.J.Crowder I will edit my statement since it might be subjective, but I've seen it mentioned several places. https://stackoverflow.com/questions/37364973/angular-promise-vs-observable – Per Hornshøj-Schierbeck Aug 02 '17 at 16:48
  • I bit quick on the vote for close - I need a solution with Observables, the phrasing can (and was) changed so it's not longer opinion based. – Per Hornshøj-Schierbeck Aug 02 '17 at 16:51
  • 1
    *"Are Observables really a good substitute for Promises"* is, prima facie, asking for opinion. *(It wasn't my close vote, btw.)* If the question is really "How can I use Observables instead of Promises?" I'd edit the question title to say that. I'd also edit to make it much more concise. (And one person expressing an opinion in an SO answer does not qualify as a reasonable justification with supporting data.) – T.J. Crowder Aug 02 '17 at 16:53
  • 1
    @T.J.Crowder Good point, title was changed. – Per Hornshøj-Schierbeck Aug 02 '17 at 16:55
  • @PerHornshøj-Schierbeck Use Promise if you want a result only once. Use Observables if you want a promise-like result, many times. Observable also gives ability to use interceptors and other neat things, whereas promises do not. At the fundamental level, these are the only differences. – Lansana Camara Aug 02 '17 at 17:04

1 Answers1

3

I think what you're looking for is AsyncSubject. It mimics the promises behavior very well. Here is the description:

The AsyncSubject is a variant where only the last value of the Observable execution is sent to its observers, and only when the execution completes.

Here is how it can be used in your case:

subject = new AsyncSubject();
ready = streamOfData(stuff).first().subscribe(subject);    
methodOne() {
    return this.subject.asObservable();
}

The subject subscribes to the underlying observable returned by the first operator and waits until it's complete. It collects all the subscribers but doesn't send any values to them. As soon as the underlying observable completes it remembers the value and sends it to the collected subscribers. All new future subscribers will be immediately passed this stored resolved value.

Here is the simple example that demonstrates that you can subscribe before or after the observable completes:

const subject = new AsyncSubject();
const o = subject.asObservable();
o.subscribe((v) => {
  console.log(v);
});
interval(500).first().subscribe(subject);

setTimeout(() => {
  o.subscribe((v) => {
    console.log(v);
  });
}, 2000);
Max Koretskyi
  • 101,079
  • 60
  • 333
  • 488
  • I should mention that the example is not loading a http request - it really is a stream that never ends. I'll update my question to reflect this more clearly. I guess i could use a .first() on my observable... I'll try it out. – Per Hornshøj-Schierbeck Aug 02 '17 at 16:58
  • @PerHornshøj-Schierbeck, so how were you supposed to use promises with the stream that never ends? Promises is all about one resolved value. – Max Koretskyi Aug 02 '17 at 17:01
  • I would wrap the first emit and resolve the promise then – Per Hornshøj-Schierbeck Aug 02 '17 at 17:03
  • @PerHornshøj-Schierbeck, okay, just use `first()` then. Updated my answer – Max Koretskyi Aug 02 '17 at 17:04
  • This behaves excatly like my own attempt with replaySubject(1).refCount(). It works with promises (just tested), but both Observable examples only work if subscription (your methodOne) is called before the first/only emit. Come to think of it, calling .first() unsubscribes, perhaps that is why? – Per Hornshøj-Schierbeck Aug 02 '17 at 17:18
  • @PerHornshøj-Schierbeck, something is wrong with your setup. Check the example I've added to my answer that shows that you can subscribe after the source observable completed – Max Koretskyi Aug 02 '17 at 17:28
  • Your example works on a static list [1,2,3] that will send a .done to the stream, my stream never ends so i think AsyncSubject won't work. Perhaps if the stream was a timer - instead of the of(1,2,3) it would mimic my setup better? I'll accept your answer and solve it with promises this time :P – Per Hornshøj-Schierbeck Aug 02 '17 at 17:31
  • @PerHornshøj-Schierbeck, `first` will complete the stream. I've replaced `of` with `interval` – Max Koretskyi Aug 02 '17 at 17:33
  • Ok it's too late - i gave it one last try and made your example work. I have no idea what went wrong the first time. Thanks :) – Per Hornshøj-Schierbeck Aug 02 '17 at 17:36