2

Since switchMap may cut off (unsubscribe) an inner Observable if a new item comes in, we expect the following:

  const items = from([1, 2])
  const seen = []
  const derived = items.pipe(
    switchMap(item =>
      concat(
        of(`now: ${item}`),
        of(`end: ${item}`).pipe(delay(10))
      )
    )
  )

  derived.subscribe(next => seen.push(next))

to result in seen having:

['now: 1', 'now: 2', 'end: 2']
           ^ unsubscribed to 1 here, as planned!

The question is, is there a way to get notified that item 1 was canceled? The reason being, if cancellations are happening too frequently I'd like to know.

I've tried ways of modifying the inner Observables, but basically anything I try to do 'at the end' of them simply doesn't happen when unsubscribed. The Observer spec has next, completed, and error, but nothing for "ended early", so I'm not sure what to try.

I'd like to be able to pass an additional function to switchMap, which will be invoked by the operator when an inner observable is canceled, and provided the argument.

  const derived = items.pipe(
    switchMap(item => concat(
      of(`now: ${item}`),
      of(`end: ${item}`).pipe(delay(10))
    ),
    (item) => console.error(`${item} was cut off.`))
  )
Dean Radcliffe
  • 2,194
  • 22
  • 29
  • 1
    I just realized this is discussed here: https://github.com/ReactiveX/rxjs/issues/2823. This is a custom operator that tells you why the chain is being disposed: https://stackblitz.com/edit/rxjs-scmt4v?file=index.ts – martin Feb 07 '19 at 17:02

1 Answers1

2

Edit: There's an issue on RxJS GitHub page discussing this feature for the finalize operator. https://github.com/ReactiveX/rxjs/issues/2823

Since switchMap unsubscribes the inner Observable it won't receive any complete notification.

Instead you can just add a dispose handler or simply said use finalize() operator that does that for you and it's called when the chain is disposed. That's on both complete and unsubscribe events.

switchMap(item =>
  concat(
    of(`now: ${item}`),
    of(`end: ${item}`).pipe(delay(10))
  ).pipe(
    finalize(() => /* switchMap unsubscribed or concat completed */),
  )
)

Note, that when you unsubscribe it means you don't want to be receiving any more items from an Observable. This means that you can't receive any new items from the unsubscribed Observable when unsubscribing. There's for example defaultIfEmpty operator that emits an item when the source completes without emitting anything which works for complete notifications but can't work you're just unsubscribing.

martin
  • 93,354
  • 25
  • 191
  • 226
  • This does not answer the question. As you write in the commented-out code, finalize is called upon completion as well, so there is still no definitive indication whether the inner observable was unsubscribed or not. – andreisrob Jan 29 '19 at 23:48