Использование оператора takeUntil
для автоматического отказа от подписки на наблюдаемое - это механизм, описанный в статье Бена Леша Не отказываться от подписки.
Это также основа общепринятого шаблона для отказа от подписки при разрушении компонента Angular.
Чтобы механизм был эффективным, операторы должны применяться в определенной последовательности. И недавно я видел код, который использовал оператор takeUntil
, но применял операторы в последовательности, которая приводила к утечкам подписки.
Давайте посмотрим на последовательность проблем и причину утечек.
Какая последовательность проблем?
Если оператор takeUntil
помещается перед оператором, который включает подписку на другой наблюдаемый источник, подписка на этот источник может не быть отменена, когда takeUntil
получит свое уведомление.
Например, такое использование combineLatest
приведет к утечке подписки на b
:
И это использование switchMap
также приведет к утечке подписки на b
:
Почему течет?
Причина утечки становится более очевидной, когда устаревший оператор combineLatest
заменяется статической фабричной функцией combineLatest
, например:
Когда notifier
излучает, наблюдаемое, возвращаемое оператором takeUntil
, завершается, автоматически отменяя подписку всех подписчиков.
Однако подписчик на c
не подписан на наблюдаемый объект, возвращаемый takeUntil
- он подписан на наблюдаемый объект, возвращаемый combineLatest
, поэтому он не отменяется автоматически после завершения takeUntil
наблюдаемого.
Подписчик на c
останется подписанным до тех пор, пока не будут выполнены все наблюдаемые, переданные в combinedLast
. Таким образом, если b
не будет завершено до отправки notifier
, подписка на b
будет протекать.
Чтобы избежать этой проблемы, по общему правилу takeUntil
должен быть последним оператором в последовательности:
При такой организации, когда notifier
излучает, подписчик на c
будет автоматически отказываться от подписки - поскольку наблюдаемое, возвращаемое takeUntil
, будет завершено, - и реализация takeUntil
отменит подписку на наблюдаемое, возвращенное combineLatest
, которое, в свою очередь, отменит подписку как на a
, так и на b
.
Использование TSLint, чтобы избежать проблемы
Если вы используете механизм takeUntil
для косвенной отмены подписки, вы можете убедиться, что это всегда последний оператор, передаваемый в канал, включив правило rxjs-no-unsafe-takeuntil
, которое я добавил в пакет rxjs-tslint-rules
.
Обновление
По общему правилу takeUntil
размещается последним. Однако есть некоторые ситуации, в которых вы можете захотеть использовать его как второй последний оператор.
RxJS включает несколько операторов, которые выдают значение, когда наблюдаемый источник, к которому они применяются, завершается. Например, когда их источники завершены, count
генерирует количество значений, выданных источником, а toArray
генерирует накопленный массив значений.
Когда наблюдаемое завершается из-за takeUntil
, такие операторы, как count
и toArray
, будут выдавать значение, только если они помещены после оператора takeUntil
.
Кроме того, есть еще один оператор, который следует разместить после takeUntil
: оператор shareReplay
.
Текущая реализация shareReplay
имеет ошибку / особенность: она никогда не откажется от подписки на свой источник. Он будет оставаться подписанным до тех пор, пока его источник не выдаст ошибку или не завершится - для получения дополнительной информации см. Этот PR - поэтому размещение takeUntil
после shareReplay
будет неэффективным.
Упомянутое выше правило TSLint знает об этих исключениях из общего правила и не приводит к неоправданным сбоям.
В RxJS версии 6.4.0 shareReplay
был изменен, так что его поведение при подсчете ссылок можно было указать с помощью параметра config
. Если указан подсчет ссылок, shareReplay
можно безопасно разместить перед takeUntil
.
Для получения дополнительной информации см. Эту статью.
Этот пост также опубликован в моем личном блоге: ncjamieson.com.