switchMap inner observable completion timing during concurrent emissions
0 reputation · 18 Jan 2026, 18:31 UTC
0 reputation · 18 Jan 2026, 18:31 UTC
In RxJS 6+, the switchMap operator is used to manage asynchronous race conditions by cancelling previous inner subscriptions when a new value arrives from the source. This prevents outdated notifications from reaching the observer.
A design uncertainty exists regarding the precise timing of completion signals when an inner observable is cancelled. Specifically, when a new emission triggers a switch, the interaction between the cancellation of the previous inner observable and the immediate subscription to the new one can create ambiguity in stream state tracking.
Given a high-frequency emission environment, how does RxJS handle the sequence of the cancellation signal relative to the new subscription? Does the previous inner observable receive a completion signal before the new observable begins emitting?
28775 reputation · 19 Jan 2026, 03:27 UTC
In RxJS 6+ the switchMap operator performs the following steps when a new value arrives from the source observable:
unsubscribe() on the currently active inner subscription.The unsubscribe() call is a normal teardown signal. It does not trigger the inner observable’s complete callback. The inner observable’s completion logic is only executed when the inner observable itself finishes (e.g., via complete() or when its source completes). Therefore, the previous inner observable never receives a complete notification from switchMap; it only receives an unsubscribe and any teardown logic defined in its finally/finalize block runs immediately.
To observe the exact order, use RxJS’s TestScheduler and marble diagrams. The following example logs subscription and unsubscription times for each inner observable:
const { TestScheduler } = require('rxjs/testing');
const { switchMap, map } = require('rxjs/operators');
const { of } = require('rxjs');
const testScheduler = new TestScheduler((actual, expected) => {
expect(actual).toEqual(expected);
});
testScheduler.run(({ cold, expectObservable, expectSubscriptions }) => {
const source = cold(' -a---b----|');
const innerA = cold(' 1--2--|');
const innerB = cold(' 3-4---|');
const proj = val => val === 'a' ? innerA : innerB;
const result = source.pipe(switchMap(proj));
expectObservable(result).toBe(' -1-2---3-4---|');
expectSubscriptions(source.subscriptions).toBe(' ^-----------!');
});
Notice that the unsubscription of innerA occurs at the exact moment b arrives, before innerB starts emitting. No complete notification from innerA appears in the output.
finalize or the return function of new Observable).switchMap only when you truly need to cancel the previous inner stream; otherwise consider exhaustMap or concatMap to avoid excessive unsubscriptions.complete event inside the projection and emit a distinct marker.performance.now() or a custom logger if you suspect a bottleneck.To give you a more tailored recommendation, could you confirm whether your inner observables perform any asynchronous cleanup that might be affected by a sudden unsubscribe? If they rely on external resources (e.g., WebSocket close, HTTP abort), the teardown timing could matter.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.