fix: chain interop/safe subscriber unsubscriptions correctly #5472
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Description:
This PR fixes a problem in the core of
SafeSubscriber
and, with the fix, thesubscribeWith
util function that was added in #5333 is no longer required.The problem is that
SafeSubscriber
behaves in a manner that is completely different to the known-to-be-safeSubscriber
. Specifically, unsubscription is not chained correctly.In
Subscriber
, the source subscription (aSubscriber
is aSubscription
) is added to the destination subscription. This is done so that when the subscriber/subscription associated with an operator has itsunsubscribe
method called, it unsubscribes from its source subscriptions.Prior to this change, the implementation of
SafeSubscriber
had this the wrong way around: it added a call to the observer'sunsubscribe
method to theSafeSubscriber
:rxjs/src/internal/Subscriber.ts
Line 191 in ced2fec
This is not correct. The destination could be a partial observer - with
next
,error
andcomplete
methods, but it could also be an interopSubscriber
- i.e. aSubscriber
that fails theinstanceof
test because it's from a different module, etc.Chaining unsubscriptions from the source to the destination is incorrect. When a source completes and is automatically unsubscribed, it should not effect the unsubscription of its destination. For example, here:
the source emits
42
, completes and the subscriber associated with theswitchMap
operator is automatically unsubscribed from the source:rxjs/src/internal/operators/switchMap.ts
Lines 141 to 147 in ced2fec
However, that subscriber's destination is not unsubscribed. If unsubscription were to be chained from the source, the callback passed to
subscribe
would be unsubscribed, too, and the flattening would be ineffectual.In fact,
switchMap
's destination is never unsubscribed from within theswitchMap
operator:rxjs/src/internal/Subscriber.ts
Lines 147 to 150 in ced2fec
The unsubscription of a destination isn't effected by the unsubscription of a source. Rx does not work like that:
unsubscribe
is not a notification.Unsubscription does not chain like a
next
,complete
orerror
notification. Unsubscription is chained in the other direction: whenunsubscribe
is called on a subscription, that subscription unsubscribes from its sources. It does not - and must not - callunsubscribe
on its destination.With this change, #5469 is fixed and
subscribeWith
is no longer required. This PR includes a failing test for the aforementioned issue and removessubscribeWith
. All tests pass - including those that were added along with thesubscribeWith
util.This also means that the change in PR #5311 should not be applied - it will not only break subjects it will also break interop subscribers - and its related issue #2675 should be closed.
unsubscribe
is not a notification and it should not be an expectation that anunsubscribe
method can be added to a partial observer to be called when a source subscription is unsubscribed. To get something like anunsubscribe
notification, callers need to use thefinalize
operator.Related issue (if exists): #5469 #5311 #2675