Какой подход лучше для уничтожения наблюдаемого, предусмотренного для оператора takeUntil, и почему?

У меня вопрос об одном из распространенных шаблонов отказа от подписки с оператором takeUntil для Angular и RxJs. В этой статье, это под третьей позицией. Например, у нас есть такой код в классе компонента:

  private destroy$: Subject<boolean> = new Subject();

  ngOnInit() {
     this.control.
     .pipe(takeUntil(this.destroy$)
     .subscribe(doSmthngFunc); 
  }

  ngOnDestroy() {
    this.destroy$.next(true);
    // Which next line of code is correct?
    // this.destroy$.complete()     // this one?
    // this.destroy$.unsubscribe()  // or this one?
  }

Первая строка this.destroy $ .next (true) полностью понятна. Но второго нет. Если мы посмотрим на реализацию этих методов, мы обнаружим, что они имеют несколько схожее поведение. complete (): отказаться от подписки ():

Насколько я понимаю, семантически complete () предпочтительнее, потому что мы вызываем next () в первый и последний раз в течение жизненного цикла компонента, а затем мы закончили с этим Subject, который рассматривается как Observable и может вызывать complete (). Эти методы принадлежат наблюдателю, а метод отказа от подписки принадлежит наблюдаемому, и у нас нет подписок, от которых можно отказаться. Но под капотом эти методы имеют похожий код:

    this.isStopped = true; // both

    this.observers.length = 0; // complete
    this.observers = null;     // unsubscribe

    this.closed = true;        // only unsubscribe

Теоретически complete () имеет отложенный эффект, поскольку он может вызывать complete () для каждого подписанного наблюдателя, но у нас нет наблюдателей на destroy $. Итак, вопрос - какой способ предпочтительнее, менее подвержен ошибкам и почему?


person Oleksandr K    schedule 05.07.2019    source источник
comment
Обе строки под комментарием Which next line of code is correct? избыточны. Требуется только this.destroy$.next(true);. Оператор takeUntil является единственным подписчиком на тему destroy$, и он откажется от подписки, как только destroy$ отправит next уведомление. Ни complete, ни unsubscribe вызывать не нужно. И вы почти никогда не хотите звонить unsubscribe по теме - см. blog.angularindepth.com/ rxjs-closed-themes-1b6f76c1b63c   -  person cartant    schedule 06.07.2019
comment
but we have no observers on destroy$ каждый раз, когда вы используете takeUntil(), добавляется наблюдатель. Наблюдатели - это вещи, которые слушают. Я думаю, вы неправильно поняли этот термин.   -  person Reactgular    schedule 06.07.2019
comment
Указанная выше ссылка на сообщение в блоге больше не действительна. Каноническая ссылка на сообщение: ncjamieson.com/closed-subjects.   -  person cartant    schedule 17.11.2020


Ответы (3)


Разрушение компонента - это единичное событие.

   this.destroy$.next();
   this.destroy$.complete();

Гарантирует, что объект излучает только один раз и завершает.

Например;

    const destroy$ = new Subject();

    destroy$.subscribe(v => console.log("destroyed"));

    destroy$.next();
    destroy$.complete();
    destroy$.next();

    // the above prints "destroyed" only once.

Это не техническое требование для выполнения, но если вы этого не сделаете, тогда бизнес-логика, которая зависит от завершения, а не от выброса, не всегда будет работать и может привести к утечке памяти.

Например, это может быть утечка памяти в RxJs.

   destroyed$.subscribe(() => {
       console.log('This might leak memory');
   });

Вышеупомянутое может привести к утечке памяти, потому что подписка никогда не заканчивается, а наблюдаемый никогда не завершается. Вы можете исправить утечку, добавив оператор first() или убедившись, что тема заполнена. RxJS не знает, что объект выдаст только одно значение, поэтому вы должны это сообщить. В противном случае подписчики остаются привязанными к кадру стека и не собираются сборщиком мусора. Таким образом, хотя сборщик мусора может собирать компонент после его использования, если что-либо ссылается на кадр стека подписчика, эта подписка продолжает существовать.

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

this.destroy $ .unsubscribe ()

Вызов отказа от подписки для субъекта может не повлиять на последующих операторов, которые создают внутренние подписки. Например, switchMap() и mergeMap() создают внутренние подписки.

Таким образом, вы не можете эффективно управлять подписками на более высоких уровнях. От созданной подписки лучше отказаться при вызове метода subscribe(), потому что он последний в цепочке операторов.

person Reactgular    schedule 06.07.2019

Я не думаю, что вам нужно делать что-то большее, чем destroy$.next();. destroy$ - это Subject, созданный вами в своем классе, и единственное, за что он отвечает, - это прерывание вашей подписки. Никто, но вашему классу разрешено трогать его (так как он частный)

Согласно статье, лучше сделать destroy$.complete(), чтобы избежать утечек памяти. Я не думаю, что использование unsubscribe() имеет смысл. Если кто-то подписался на destroy$, вы должны сохранить это в подписке и отказаться от подписки в ngOnDestroy()-методе вызывающего абонента. Однако, поскольку вы используете takeUntil, отказываться от подписки не нужно.

person John    schedule 05.07.2019

Оба утверждения дадут желаемый эффект. Afaik, нет никаких технических причин не использовать unsubscribe. Семантически - как вы упомянули - гораздо больше смысла использовать complete.

Субъект одновременно является наблюдаемым (посылающим) и наблюдателем (слушающим), и эта двойственность отражена здесь. next и complete оба принадлежат к "отправляющей" стороне вещей. complete сигнализирует, что этот поток больше не будет отправлять значения. unsubscribe является частью интерфейса "прослушивания" и имеет, соответственно, другое значение: "не уведомлять меня о дальнейших выбросах".

Изменить: при повторном прочтении я вижу, что вы уже включили это различие в вопрос, поэтому мой ответ, вероятно, не добавляет вам многого :(. Я действительно думаю, что семантика достаточно важна в своем имеют право использовать здесь полную отмену подписки и не видят фактического риска использования одного вместо другого в этом шаблоне. Надеюсь, что это все еще в некоторой степени полезно!

person D. Veen    schedule 05.07.2019
comment
unsubscribe и complete семантически не одинаковы. Complete будет распространяться на дочерних наблюдателей, но отказ от подписки просто остановит передачу. Ваш ответ подразумевает, что вы можете использовать unsubscribe без побочных эффектов, но я не думаю, что это правда. githubrb.com/Reactive/ - person Reactgular; 06.07.2019
comment
Я хочу сказать, что они семантически НЕ одинаковы. И я рекомендую использовать здесь complete. Что вы имеете в виду, когда говорите, что отказ от подписки вызовет побочные эффекты? Я имею в виду в данном конкретном случае. - person D. Veen; 06.07.2019