1

I was playing around with the switchMap operator to clearly understand what was happening to a "switched" inner observable.

At first i thought that switchMap was only "unsubscribing" from the switched inner observable, but then i realize it was in fact "unsubscribing AND completing" the inner observable.

To confirm that i've written this small snippet: https://codesandbox.io/s/relaxed-meninsky-c5jmw?fontsize=14

As you can see, the finalize() operator is correctly called when the subject emit for the second time, but:
why does the complete handler of the tap operator is not called ?

This somehow make feel only 80% happy with my understanding of this operator.

A related not on that:
I've read and watch numerous sources regarding switchMap, including:

And none of them clearly state if inner observable is unsubscribed or unsubcribed AND closed ( or at least i did not understand it :) )

I've watched the switchMap operator source code and there is no mention to takeXXX operator, how can he complete the inner operator without that ?

tl;dr

  • Do you confirm that switchMap complete inner observable when switching ?
  • Why does tap operator does not work as expected ?
  • If switchMap effectively complete inner observable how can he do that without using a takeXXX operator internally ?
Clement
  • 3,860
  • 4
  • 24
  • 36
  • When an Observable has no more observers, it completes. – Will Alexander Aug 28 '19 at 16:21
  • Not sure about that assertion @WillAlexander, are you talking in general or in the context of the question ? – Clement Aug 28 '19 at 18:24
  • I apologise, I misspoke. What I meant to say is that when an Observable has no more subscribers, it calls any `finalize` callback and stops emitting. It doesn't technically complete. – Will Alexander Aug 29 '19 at 08:04
  • You're right, look at this snippet: https://codesandbox.io/s/switchmap-and-tap-issue-7sb1v?fontsize=14 What's weird is that the rxjs doc say that the finalize operator is called on complete or error, not on "unsubscribe" – Clement Aug 29 '19 at 09:51

1 Answers1

2

I think you are confusing the difference between unsubscribe() and complete(). For a hot observable like a Subject you can "stop" it in a few ways. From the 'top->down' with complete() as you did in your example, or from the 'bottom->up' with unsubscribe().

switchMap() does exactly what it says, it switches from the primary observable to a secondary (or 'inner') observable. That is why when you complete() the outer observable, it has no effect on the inner one - the chain has been switched. To affect the chain (as opposed to just affecting the Subject which is the source observable), you need to get a reference to the Subscriber, and then call that Subscriber's unsubscribe() method.

To see this, I've forked your CodeSandbox and produced this new one

As you will see in that CodeSandbox I have added a few more lines to show what is going on:

  • Note the new tap() in the chain right above the switchMap - this will show what is going on directly from the Subject() before the chain is switched to a different Observable with the switchMap operator.
  • The Subscription for the chain is now being captured in the variable sub which can be unsubscribed later to affect the chain from the bottom->up.
  • Note that the s.complete() after 10 seconds is now reflected in the Subject, and note also how it doesn't affect the chain at all.
  • Now note that the new sub.unsubscribe() after 15 seconds indeed kills the chain.
  • uncomment the take(5) in the newT() method to see that indeed the tap's complete method will be called if the source above it actually completes (top->down).

finalize() catches the fact that an unsubscribe has happened (bottom->up), note that it occurs both when switchMap() does the automatic unsubscribe upwards when s.next() is called on the Subject source, as well as when unsubscribe() is called on the Subscription, which again causes a bottom->up termination. In no case is your complete() called in the original observer because the chain is never actually completed. You can complete the chain with a take(10) operator if you want, to see how that works as well.

Hopefully this helps clear up the confusion a little. :)

dmcgrandle
  • 5,934
  • 1
  • 19
  • 38
  • For me complete an observable and unsubscribing from an observer are 2 differents things. The Rxjs doc about finalize() say: "Returns an Observable that mirrors the source Observable, but will call a specified function when the source terminates on complete or error." What's "bug" me is that the doc is saying that finalize is called on complete, but in here, finalize is called not on complete, but on "unsubscribe". Seams like this is introducing an new "state" like: "not completed but without subscription" – Clement Aug 29 '19 at 09:38
  • 1
    Look at this snippet: codesandbox.io/s/switchmap-and-tap-issue-7sb1v?fontsize=14 IMHO this confirm what you say and what @WillAlexander is also saying. finalize is called when a subscription get unsuscribed, which does not imply that the observer complete. So i would be tempted to say that the doc is not 100% accurate regarding how finalize is called. – Clement Aug 29 '19 at 09:56
  • I think `finalize` is like the `ngOnDestroy` of Observables. If you look at timing, it even gets called _after_ any `complete` callback in a subscription. – Will Alexander Aug 29 '19 at 09:59
  • Seams like it's something already known: https://github.com/ReactiveX/rxjs/issues/4578 – Clement Aug 29 '19 at 14:27
  • Good catch Clement, I will follow that issue on github and see where this lands. :) – dmcgrandle Aug 29 '19 at 16:06