Нужно ли отписываться / завершать в бэкэнд-сервисе для предотвращения побочных эффектов? - PullRequest
2 голосов
/ 08 апреля 2020

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

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


КОНЦЕПЦИЯ:

// some.component. ts (очень упрощенно)

this.someSub = this.backendService.someStuff.subscribe(...);

ngOnDestroy() {
  this.someSub.unsubscribe(); // I hope we agree that it is best practice to unsubscribe here, but anyway this is not the question
}

// backend.service.ts

export class BackendService {
  private _someStuff = new BehaviorSubject<any>(null);
  readonly someStuff = this._someStuff.asObservable();

  constructor(http: HttpClient) { }

  getStuff() {
    this.http.get('...').subscribe( response => this._someStuff.next(response) );

  }

ВОПРОС:

Вышесказанное - это то, что я нашел хорошим решением из моих расследований, чтобы не пропускать подписки HTTP из службы, но было также много дискуссий о том, что вы должны отписаться от наблюдаемых форм HTTP, даже если они завершены. самих себя. Поэтому я предполагаю, что это верно и для услуг. Но я никогда не видел, чтобы HTTP-запросы в сервисах были отписаны, а темы не были выполнены. Так как я ожидаю, что это необходимо, следующий код показывает backenService, как и следовало ожидать:

// backend.service.ts

export class BackendService implements OnDestroy {                      // A. //
  httpSubs = new Subscription();                                        // B1. //

  private _someStuff = new BehaviorSubject<any>(null);
  readonly someStuff = this._someStuff.asObservable();

  constructor(http: HttpClient) { }

  getStuff() {
    this.httpSubs.add(this.http.get('...')                              // B2. //
      .subscribe( response => this._someStuff.next(response) )
    );     
  }

  // rest of CRUD implementations
  // ...

  ngOnDestroy(): void {                                                               
    this.httpSubs.unsubscribe();                                        // B3. //
    this._someStuff.complete();                                         // C. //
  }

Я отметил вещи (A .- C.), Которые не такие, как я видел в общих руководствах и прочем.
Если я не пропущу что-то фундаментальное в отношении компонентов, услуг и того, как пропущенные отписки могут вызывать побочные эффекты ... тогда мои вопросы относительно необходимости отмены / завершения в сервисе:

  1. Я ожидаю, что теперь у нас есть подписка в сервисе, здесь обязательно реализовать OnDestroy ( A. ), так как мы делаем в компонентах, чтобы можно было правильно отписаться. Я полагаю, что это даже тот случай, когда служба предоставляется в root (и поэтому будет уничтожена как одна из последних частей приложения), так как возможно, что служба вызовет побочные эффекты в другой службе root, которая также не мусор еще не собран.
    Это правильно?

  2. Если мы не отписаться в серве ie, мы столкнемся с те же проблемы, что и при отказе от подписки в компоненте. Поэтому добавляются B 1 - 3 (на самом деле, тип отписки не важен - только факт, который мы делаем - я выбираю нативный подход Rx JS)
    Это правильно? ?

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

Я не знаю, как .asObservable работает внутри, но также должны быть подписки.
Так что необходимо завершить предметы по поведению (или нормальные предметы), такие как _someStuff ( C.)?

1 Ответ

2 голосов
/ 09 апреля 2020

Если наблюдаемое (например: из HttpClient) завершается, нет необходимости отписываться.

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

Например, когда у вас есть что-то вроде

http.get(...)
 .pipe(
  a(),
  b(),
  c(),
 ).subscribe(observer)

За кадром происходит несколько интересных вещей. Прежде всего observer преобразуется в subscriber. Класс Subscriber расширяет Subscription, который содержит логи отмены подписки c.

Каждый оператор (a, b и c) в цепочке будет свой собственный экземпляр Subscriber, потому что после первого subscribe будет подписана также каждая наблюдаемая, возвращаемая их оператором (оператор - это функция, которая получает наблюдаемую и возвращает наблюдаемую) , В результате получается цепочка Subscriber с.

// S{n} -> Subscriber {n} (in the order in which they are created)
http.get(...)
 .pipe(
  a(), // S4
  b(), // S3
  c(), // S2
 ).subscribe(observer) // S1

S1 - родитель ( destination ) S2, S2 - родитель S3 и т. Д.

HttpClient.get по существу совпадает с

new Observable(subscriber => {
 // Make request

 // On request ready
 subscriber.next(resultOfTheRequest)
 subscriber.complete(); // So here completes!
})

Исходный код

Важно отметить, что в этом случае параметр subscriber будет равен S4.

subscriber.complete() совпадает с S4.complete(). Когда это происходит, S4 будет передавать полное уведомление на S3, которое, в свою очередь, будет передавать уведомление на S2 и так далее до тех пор, пока S1 не получит уведомление. На этом этапе он отменит подписку .

Когда подписчик откажется от подписки , все его потомки также будут отписаны.

Итак, отписаться от закрытого абонента (становится closed после отмены подписки) безобидно, но избыточно .

1.

Если услуга предоставляется на уровне root ( например: providedIn: 'root'), его ngOnDestroy не будет вызываться. Насколько я заметил, он будет вызываться только в том случае, если служба будет уничтожена, как в случае, если вы предоставили ее на уровне компонента .

Вот демонстрация, иллюстрирующая это.

IMO, если услуга предоставляется на уровне root, вам не следует беспокоиться о том, чтобы отписаться от наблюдаемых. Однако, если ваша услуга предоставляется на уровне компонентов, вам следует отписаться.

2.

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

Если ваши наблюдаемые не завершают сами по себе (например, fromEvent), и услуга не предоставляется при root уровень , вы должны вручную отписаться в ngOnDestroy.

3.

A Subject будет вести список активных подписчиков . Когда активный подписчик становится, неактивным , он будет удален из списка подписчиков.

Например, если у вас есть BehaviorSubject в root -обеспечиваемой услуге, вам не стоит беспокоиться об отказе от ссылок.

На самом деле, более безопасный способ сделать это - позвонить по номеру Subject.unsubscribe(). Subject.complete() отправит полное уведомление своим подписчикам, а затем очистит список.

...