В этой статье рассматривается unsubscribe метод Subject - и его производные классы - так как он имеет удивительное поведение.

Подписки

Если вы посмотрите на подпись для Observable.prototype.subscribe, вы увидите, что она возвращает Subscription. А если вы использовали наблюдаемые объекты, то вы знакомы с вызовом метода unsubscribe подписки. Однако подписка содержит не только метод unsubscribe.

В частности, класс Subscription реализует интерфейс ISubscription:

Где AnonymousSubscription - тот же интерфейс, но без свойства только для чтенияclosed.

Свойство closed указывает, была ли отписана подписка - вручную или автоматически (если наблюдаемое завершается или возникает ошибка).

Подписчики и отписка

Интересно, что в результате вызова subscribe возвращается экземпляр класса Subscriber, который расширяет класс Subscription.

Подобно методу subscribe, классу Subscriber можно передать частичного наблюдателя или отдельные функции обратного вызова next, error и complete.

Основная цель Subscriber - гарантировать, что методы наблюдателя или функции обратного вызова вызываются только в том случае, если они указаны, и гарантировать, что они не будут вызваны после вызова unsubscribe или наблюдаемого источника completes или errors.

Можно создать Subscriber экземпляр и передать его в subscribe вызове, поскольку Subscriber реализует Observer интерфейс. Subscriber будет отслеживать подписки, которые происходят из таких subscribe вызовов, и unsubscribe может быть вызван либо для Subscriber, либо для возвращенного Subscription.

Также можно передать экземпляр более чем в одном subscribe вызове, а вызов unsubscribe на Subscriber отменит его подписку на все наблюдаемые объекты, на которые он подписан, и пометит его как закрытый. Здесь вызов unsubscribe отменит подписку как на one, так и на two:

Так какое отношение это имеет к предметам? Ну, испытуемые по-разному ведут себя.

Предметы

Субъект одновременно является наблюдателем и наблюдаемым. Класс Subject расширяет класс Observable и реализует интерфейс Observer. Он также реализует интерфейс ISubscription, поэтому субъекты имеют свойство closed только для чтения и метод unsubscribe.

Его реализация ISubscription предполагает, что - как и в случае с Subscriber - должна быть возможность подписаться и отписаться от Subject, например:

Однако произойдет ошибка:

ObjectUnsubscribedError: object unsubscribed
  at new ObjectUnsubscribedError
  at Subject.next
  at SubjectSubscriber.Subscriber._next
  at SubjectSubscriber.Subscriber.next
  at MapSubscriber._next
  at MapSubscriber.Subscriber.next
  at AsyncAction.IntervalObservable.dispatch
  at AsyncAction._execute
  at AsyncAction.execute
  at AsyncScheduler.flush

Почему? Что ж, метод unsubscribe в классе Subject на самом деле ничего не отменяет. Вместо этого он помечает субъект как closed и устанавливает для своего внутреннего массива подписанных наблюдателей - Subject extends Observable, помните - null.

Субъекты отслеживают наблюдателей, которые подписаны на тему, но в отличие от подписчиков, они не отслеживают наблюдаемые объекты, на которые подписан сам субъект, поэтому субъекты не могут отказаться от подписки на свои источники.

Так почему же возникает ошибка? Ошибка выдается субъектом, когда его метод next, error или complete вызывается после того, как он был помечен как closed и поведение является преднамеренным:

Если вы хотите, чтобы объект громко и сердито выдавал ошибку, когда вы рядом с ним после того, как он будет полезен, вы можете вызвать функцию отмены подписки непосредственно в самом экземпляре объекта. - Бен Леш

Такое поведение означает, что если вы вызываете unsubscribe по теме, вы должны быть уверены, что она либо отписана от своих источников, либо что источники завершены или содержат ошибки.

Меры предосторожности

Учитывая такое удивительное поведение, вы можете запретить или предупредить о вызовах unsubscribe по темам. Если вы это сделаете, мой rxjs-tslint-rules пакет включает правило, которое делает именно это: rxjs-no-subject-unsubscribe.

Правило также предотвращает передачу тем в add метод подписки - метод, который станет темой будущей статьи о составе подписки.

Этот пост также опубликован в моем личном блоге: ncjamieson.com.