DevGang
Авторизоваться

Angular для младших разработчиков: горячие и холодные наблюдаемые

Наблюдаемые объекты - это источники информации, получаемой с течением времени.

Информация здесь может называться “события”, “значения” или просто “выбросы”.

Давайте возьмем 2 вещи из реального мира в качестве примеров наблюдаемых:

  1. Радиостанция, транслирующая музыку. Никакой рекламы, только песни (да, это всего лишь воображаемый пример);
  2. Карманный музыкальный плеер. У него есть только один выход — для наушников. Каждый раз, когда мы нажимаем “play”, этот проигрыватель создает новый плейлист и начинает воспроизводить песни из этого плейлиста.

В обоих примерах, чтобы нажать кнопку “play”, мы должны вызвать метод subscribe().

“Радиостанция” - это “горячая” наблюдаемая.

Слушатели радиостанции не могут знать, сколько песен было сыграно до того, как они начали слушать.

Они не могут знать, сколько песен будет воспроизведено после того, как они вызовут unsubscribe() .

Что им интересно, так это послушать несколько следующих песен — после этого они выключат радио.

Радиостанция не будет реагировать на каждого нового слушателя — список воспроизведения не будет приостановлен или перезапущен, когда новый слушатель включает или выключает свое радио.

Наш карманный музыкальный плеер - это “холодный” наблюдаемый.

По умолчанию у него есть только один слушатель.

Слушатель точно знает, что первая песня будет воспроизведена после вызова subscribe(), и что после unsubscribe() (кнопка “stop”) песни воспроизводиться не будут.

Теперь давайте используем эти примеры с Angular.

В Angular каждый @Output() является горячим наблюдаемым

Что это означает на практике:

  1. Вам не следует ожидать, что события будут отправляться только после вашего вызова subscribe(). Это довольно важная вещь, которую нужно усвоить;
  2. Когда вы вызываете subscribe(), “плейлист” не будет сгенерирован для вас повторно. Новый процесс не будет создан для каждого подписчика. Это важно для производительности — вы можете вызывать subcribe() столько раз, сколько захотите, и сохранять столько слушателей, сколько захотите;
  3. Когда вы выбираете unsubscribe(), это не остановит отправителя. После этого компонент будет продолжать выдавать новые значения.

Как вы можете видеть, это именно то, что ожидается от @Outputs() — компонент не может предсказать, сколько наблюдателей у него будет, и это не должно влиять на производительность.

Другим источником горячих наблюдаемых в Angular является Router.

Есть ли холодные наблюдаемые объекты в Angular "из коробки"? Конечно:

Каждый HTTP-запрос в Angular является холодным наблюдаемым.

Что это означает на практике:

  • Каждый раз, когда вы выбираете subscribe() (включая использование канала “async”), будет создаваться и выполняться новый запрос. Это очень важная вещь, которую нужно понять и запомнить. Это самый большой источник проблем с производительностью и ошибок побочных эффектов с холодными наблюдаемыми;
  • Вы можете ожидать, что ответ будет отправлен после subscribe(), так что вы его не пропустите;
  • Если вы вызовете unsubscribe(), никаких будущих ответов ожидаться не будет, даже если первый ответ еще не был получен — запрос будет отменен.

Вы можете создать холодную наблюдаемую в своем коде.

Если у вас есть функция, которая создает и возвращает наблюдаемый объект — это холодный наблюдаемый объект.

Пожалуйста, запомните это правило. Иногда холодный observable - это именно то, что вам нужно, но вы должны быть уверены в этом на 100%, чтобы избежать ошибок и проблем с производительностью.

Если какая—либо функция создает наблюдаемый объект только один раз или возвращает наблюдаемый объект, созданный вне этой функции - это должен быть горячий наблюдаемый объект (Subject, ReplaySubject, BehaviorSubject).

Когда вы используете async pipe в своем шаблоне, вы должны предоставлять этому каналу только горячие наблюдаемые объекты — потому что вы не можете гарантировать, что эта часть шаблона будет сгенерирована только один раз (особенно если она находится внутри структурных директив, таких как ngIf или ngFor).

Холодные наблюдаемые объекты могут быть преобразованы в горячие наблюдаемые объекты.

Одним из хороших примеров такого преобразования является кэширование HTTP-запросов.

Допустим, у вас есть метод getUsers() в вашем ApiService, который возвращает список пользователей и генерирует какой-то очень важный и объемный отчет:

getUsers(): Observable<User[]> {
  return this.http.get<User[]>('/api/users').pipe(
     tap((users) => this.createPDFReport(users))
  );
}

Для простоты давайте опустим недействительность кэша в этом примере.

Если мы поделимся результирующим observable, то каждый подписчик вызовет новый HTTP — запрос и вызовет генератор отчетов в формате PDF - это не то, чего мы хотим.

Чтобы иметь возможность безопасно поделиться этим, мы должны добавить:

getUsers(): Observable<User[]> {
  return this.http.get<User[]>('/api/users').pipe(
     tap((users) => this.createPDFReport(users)),
     // 👇
     shareReplay({bufferSize: 1, refCount: false})
  );
}

Мы должны использовать shareReplay(), а не просто share() — в противном случае новые подписчики не получат ответ, если он был получен до того, как они подписались.

bufferSize: 1 здесь определяет, сколько предыдущих значений должно быть отправлено новому подписчику (если есть какие-либо предыдущие значения). Мы хотим кэшировать только последний ответ, поэтому используем значение 1.

refCount: false здесь означает, что запрос не будет пересоздан, если в какой-то момент количество подписчиков упало до нуля.

Возможно, вы захотите добавить shareReplay() к некоторым вашим холодным наблюдаемым, созданным не только из HTTP-вызовов.

Иногда вам все еще нужен некоторый код (“побочный эффект”), который должен выполняться при каждой подписке. Вы можете поместить его после shareReplay():

getUsers(): Observable<User[]> {
  return this.http.get<User[]>('/api/users').pipe(
     tap((users) => this.createPDFReport(users)),
     shareReplay({bufferSize: 1, refCount: false}),
     // 👇
     tap((users) => this.someExample(users))
  );
}

Если ваши подписчики должны получать значения, созданные только после того, как они подписались (например, изменения входных данных формы), тогда используйте share() вместо shareReplay().

Давайте воспользуемся правилом, которое упоминалось ранее:

Если у вас есть функция, которая создает и возвращает наблюдаемый объект — это холодный наблюдаемый объект

Наш getUsers() по-прежнему создает и возвращает наблюдаемый объект. Мы не используем повторно однажды созданный hot observable. Давайте это исправим:

private usersCached$?: Observable<User[]>;

getUsersCached(): Observable<User[]> {
  if (this.usersCached$) {
     return this.usersCached$;
  }
  this.usersCached$ = this.getUsers();
  return this.usersCached$;
}

Теперь getUsersCached() возвращает горячую наблюдаемую, потому что эта наблюдаемая была создана вне функции, и она использует shareReplay(), чтобы не перезапускать производителя.

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

#Angular #Начинающим #RxJS
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

В этом месте могла бы быть ваша реклама

Разместить рекламу