25

I'm trying to trigger a callback when all my observables are executed. In my other, older project i used finally like so and that worked like a charm:

this.myService.callDummy()
  .finally(() => console.log('Works!'))
  .subscribe(result => ...)

But now I'm using a newer version of RxJS with Pipeable operators, but the finally call (now renamed to finalize) never gets executed. There is little information to be found and I'm not sure what I'm doing wrong.

combineLatest(
  this.route.queryParams,
  this.myService.callDummy1(),
  this.myService.callDummy2()
)
.pipe(finalize(() => console.log('Does not work!')))
.subscribe(results => ...);

Any help is appreciated.

Marco
  • 1,073
  • 9
  • 22
Nico Van Belle
  • 4,911
  • 4
  • 32
  • 49

3 Answers3

38

In observables, firing and completing are not the same thing.

Even though each of the items emits a value, route.queryParams by definition will never complete since that is how Angular implements it, as a non terminating observable. You will need to manually complete it for your finalize to execute since combineLatest will only complete when EVERY observable being combined inside of it has completed.

combineLatest(
  this.route.queryParams.pipe(take(1)), // take(1) will complete the observable after it has emitted one value
  this.myService.callDummy1(),
  this.myService.callDummy2()
)
.pipe(finalize(() => console.log('Does not work!')))
.subscribe(results => ...);

This will complete.

Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
bryan60
  • 28,215
  • 4
  • 48
  • 65
  • 3
    From RxJS 5.5 you should not use `.take()` (patch prototype), instead use `.pipe(take())` – Mick Apr 09 '18 at 21:50
  • Minor detail for the purpose of this question – bryan60 Apr 09 '18 at 22:17
  • 2
    In the end `take(1)` did the trick, but I also replaced `combineLatest` with `forkJoin` as it's functionality is done by `take` and also it expects the observables to complete. Doing this, it was easier to debug and see what observables did not yet complete. – Nico Van Belle Apr 10 '18 at 07:36
  • @NicoVanBelle you mark this answer as accepted answer while mine has the same informations but is way more detailed – Mick Apr 10 '18 at 12:48
  • @NicoVanBelle I prefer forkJoin when I expect my observables to complete after 1 emission and combineLatest when I expect them to keep emitting. It's much more explicit what's happening – bryan60 Apr 10 '18 at 13:24
  • @Mick I know both answers state pretty much the same so I can only accept the answer that helped me the most in my own opinion. I cannot accept 2 answers. This said, I upvoted your answer and appreciate the help provided by you. – Nico Van Belle Apr 11 '18 at 05:44
  • He is not using pipe so it will import much more code than needed. Also first() ist more clear. – Mick Apr 11 '18 at 08:01
  • @Mick again, minor details that are not important to the issue in this question. The question wasn't about reducing import size or the "correct" method of applying rx operators, it was about observable completion. first() vs take(1) is developer preference and nothing more. Stop being so bent out of shape over 15 SO points and go answer some questions – bryan60 Apr 11 '18 at 14:03
  • So you consider to answer questions not as detailed as possible? And not give additional tips if you see errors? – Mick Apr 11 '18 at 14:16
1

Are you sure one of combined Observables actually completes? With either .complete or .error?

If none of the combined Observables completes, finally will never be called.

Roberto Zvjerković
  • 9,657
  • 4
  • 26
  • 47
  • Yes they all complete. The only issue with this code is that `finalize` does not work. – Nico Van Belle Apr 09 '18 at 15:24
  • `this.route.queryParams` does not complete – Mick Apr 09 '18 at 21:59
  • 1
    As @Mick mentioned, they returned values, but did not actually complete. I was under the false impression they completed as I was using `combineLatest` but that's just the thing; when using this the observables don't have to complete. – Nico Van Belle Apr 10 '18 at 07:55
0

If you want to do something when the observable completes then use the complete callback instead of finally/finalize:

.subscribe(
    value => { console.log(`Next: ${value}`); },
    error => { console.log(`Error: ${error}`); },
    ()    => { console.log(`Completed`); }
);

Anyway finally/finalize should also work and will be called on error OR complete. Im pretty sure that your observable never completes. You can confirm this by using my code above.

I see you are using Angular and subscribing to this.route.queryParams which never completes. You may create a new observable by use of first() so you get the value and it immediatly completes: this.route.queryParams.pipe(first())

Marco
  • 1,073
  • 9
  • 22
Mick
  • 8,203
  • 10
  • 44
  • 66
  • 5
    This will only work for a successful payload response, if `value` is returned. It will not work if `error` was returned. – sktwentysix Mar 13 '20 at 11:08