Получите текущее значение от Observable без подписки (просто хотите значение один раз)

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

Ответ 1

Вам нужно использовать BehaviorSubject,

  • BehaviorSubject похож на ReplaySubject, за исключением того, что он только запоминает последнюю публикацию.
  • BehaviorSubject также требует, чтобы вы предоставили ему значение по умолчанию T. Это означает, что все подписчики сразу получат значение (если он уже не завершен).

Это даст вам последнее значение, опубликованное Observable.

BehaviorSubject предоставляет свойство getter с именем value, чтобы получить самое последнее значение, прошедшее через него.


PLUNKER DEMO

  • В этом примере значение "a" записывается в консоль:

  //Declare a Subject, you'll need to provide a default value.
  var subject: BehaviorSubject<string> = new BehaviorSubject("a");

ИСПОЛЬЗОВАНИЕ:

console.log(subject.value); // will print the current value

Ответ 2

Быстрый ответ:

... Мне нужно только одно текущее значение, а не новые значения, поскольку они поступают...

Вы по-прежнему будете использовать subscribe, но с pipe(take(1)), так что он даст вам одно значение.

например. myObs$.pipe(take(1)).subscribe(value => alert(value));

Также см.: Сравнение между first(), take(1) или single()


Более длинный ответ:

Общее правило: вы должны когда-либо получать значение из наблюдаемого только с помощью subscribe()

(или асинхронный канал при использовании Angular)

BehaviorSubject определенно имеет свое место, и когда я начинал с RxJS, я часто делал bs.value(), чтобы получить значение. Поскольку ваши потоки RxJS распространяются по всему вашему приложению (и то, что вы хотите!), Это будет становиться все труднее и сложнее. Часто вы действительно видите, что .asObservable() используется для "скрытия" базового типа, чтобы кто-то не мог использовать .value() - и поначалу это будет казаться плохим, но вы начнете понимать, почему это было сделано со временем. Кроме того, вам рано или поздно понадобится что-то, что не является BehaviorSubject, и не будет способа сделать это так.

Вернемся к первоначальному вопросу. Особенно, если вы не хотите "обманывать", используя BehaviorSubject.

Лучше всего всегда использовать subscribe, чтобы получить значение.

obs$.pipe(take(1)).subscribe(value => { ....... })

ИЛИ

obs$.pipe(first()).subscribe(value => { ....... })

Разница между этими двумя заключается в том, что first() будет ошибкой , если поток уже завершен, и take(1) не будет излучать никаких наблюдаемых, если поток завершен или не имеет значения, доступного сразу.

Примечание. Это считается лучшей практикой, даже если вы используете BehaviorSubject.

Однако, если вы попробуете приведенный выше код, наблюдаемое "значение" будет "застревать" внутри закрытия функции подписки, и вам вполне может понадобиться это в текущей области видимости. Один из способов обойти это, если вам действительно нужно, это:

const obsValue = undefined;
const sub = obs$.pipe(take(1)).subscribe(value => obsValue = value);
sub.unsubscribe();

// we will only have a value here if it was IMMEDIATELY available
alert(obsValue);

Важно отметить, что вызов подписки выше не ожидает значения. Если в данный момент ничего не доступно, функция подписки никогда не будет вызвана, и я специально сделал вызов отмены подписки, чтобы предотвратить его "появление позже".

Так что это не только выглядит очень неуклюже - оно не будет работать для чего-то, что не сразу доступно, например, значение результата от вызова http, но на самом деле это будет работать с субъектом поведения (или, что более важно, с чем-то, что * вверх по течению и известен как BehaviorSubject ** или combineLatest, который принимает два значения BehaviorSubject). И определенно не занимайтесь (obs$ as BehaviorSubject) - тьфу!

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

Лучший подход

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

например. Допустим, мы хотим сделать отчет о наших животных, если ваш зоопарк открыт. Вы можете подумать, что вам нужно "извлеченное" значение zooOpen$, например:

Плохой путь

zooOpen$: Observable<boolean> = of(true);    // is the zoo open today?
bear$: Observable<string> = of('Beary');
lion$: Observable<string> = of('Liony');

runZooReport() {

   // we want to know if zoo is open!
   // this uses the approach described above

   const zooOpen: boolean = undefined;
   const sub = this.zooOpen$.subscribe(open => zooOpen = open);
   sub.unsubscribe();

   // 'zooOpen' is just a regular boolean now
   if (zooOpen) 
   {
      // now take the animals, combine them and subscribe to it
      combineLatest(this.bear$, this.lion$).subscribe(([bear, lion]) => {

          alert('Welcome to the zoo! Today we have a bear called ' + bear + ' and a lion called ' + lion); 
      });
   }
   else 
   {
      alert('Sorry zoo is closed today!');
   }
}

Так почему это так плохо?

  • Что если zooOpen$ исходит от веб-службы? Как будет работать предыдущий пример? На самом деле не имеет значения, насколько быстро работает ваш сервер - вы никогда не получите значение с указанным выше кодом, если zooOpen$ был http-наблюдаемым!
  • Что делать, если вы хотите использовать этот отчет "за пределами" этой функции. Теперь вы заблокировали alert в этом методе. Если вам нужно использовать отчет в другом месте, вам придется продублировать его!

Хороший способ

Вместо того, чтобы пытаться получить доступ к значению в вашей функции, рассмотрите функцию, которая создает новый Observable и даже не подписывается на него!

Вместо этого он возвращает новое наблюдаемое, которое можно использовать "снаружи".

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

getZooReport() {

  return this.zooOpen$.pipe(switchMap(zooOpen => {

     if (zooOpen) {

         return combineLatest(this.bear$, this.lion$).pipe(map(([bear, lion] => {

                 // this is inside 'map' so return a regular string
                 return "Welcome to the zoo! Today we have a bear called ' + bear + ' and a lion called ' + lion;
              }
          );
      }
      else {

         // this is inside 'switchMap' so *must* return an observable
         return of('Sorry the zoo is closed today!');
      }

   });
 }

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

 const zooReport$ = this.getZooReport();
 zooReport$.pipe(take(1)).subscribe(report => {
    alert('Todays report: ' + report);
 });

 // or take it and put it into a new pipe
 const zooReportUpperCase$ = zooReport$.pipe(map(report => report.toUpperCase()));

Обратите внимание на следующее:

  • Я не подписываюсь до тех пор, пока мне не понадобится - в этом случае вне функции
  • Наблюдаемой "движущей силой" является zooOpen$, которая использует switchMap для "переключения" на другую наблюдаемую точку, которая в конечном итоге возвращается из getZooReport().
  • То, как это работает, если zooOpen$ когда-либо изменяется, то отменяет все и начинается снова внутри первого switchMap. Подробнее об этом читайте в switchMap.
  • Примечание. Код внутри switchMap должен возвращать новую наблюдаемую. Вы можете сделать это быстро с помощью of('hello') или вернуть другое наблюдаемое, например, combineLatest.
  • Аналогично: map должен просто возвращать обычную строку.

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

Еще одно последнее замечание: если вы используете этот подход с Angular, вы можете получить вышеупомянутый отчет по зоопарку без единого subscribe, используя канал | async. Это отличный пример принципа "не подписывайся, пока не имеешь" на практике.

// in your angular .ts file for a component
const zooReport$ = this.getZooReport();

и в вашем шаблоне:

<pre> {{ zooReport$ | async }} </pre>

Смотрите также мой ответ здесь:

fooobar.com/questions/106960/...

Также не упомянуто выше, чтобы избежать путаницы:

  • tap() иногда может быть полезен, чтобы "получить значение из наблюдаемого". Если вы не знакомы с этим оператором, прочитайте его. RxJS использует "каналы", а оператор tap() - это способ "подключиться к каналу", чтобы увидеть, что там.

Ответ 3

Не уверен, что это то, что вы ищете. Вероятно, напишите, что поведение субъекта в службе. Объявите это как частное и выставьте только значение, которое вы установили. Что-то вроде этого

 @Injectable({
   providedIn: 'root'
 })
  export class ConfigService {
    constructor(private bname:BehaviorSubject<String>){
       this.bname = new BehaviorSubject<String>("currentvalue");
    }

    getAsObservable(){
       this.bname.asObservable();
    }
 }

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

Ответ 4

const observable = of('response')

function hasValue(value: any) {
  return value !== null && value !== undefined;
}

function getValue<T>(observable: Observable<T>): Promise<T> {
  return new Promise(resolve => {
    observable
      .pipe(
        filter(hasValue), // filter null or undefined out
        first()
      )
      .subscribe(resolve);
  });
}

const result = await getValue(observable)
// Do the logic with the result
// .................
// .................
// .................

Вы можете проверить полную статью о том, как реализовать это здесь. https://www.imkrish.com/how-to-get-current-value-of-observable-in-a-clean-way/

Ответ 5

Используйте конструктор Observable для создания наблюдаемого потока любого типа. Конструктор принимает в качестве аргумента функцию подписчика, которая запускается, когда выполняется метод подписки observables(). Функция подписчика получает объект Observer и может публиковать значения в методе наблюдателя next(). Попробуйте это

@Component({
  selector: 'async-observable-pipe',
  template: '<div><code>observable|async</code>: Time: {{ time | async }} . 
</div>'
})
export class AsyncObservablePipeComponent {
  time = new Observable<string>((observer: Observer<string>) => {
    setInterval(() => observer.next(new Date().toString()), 1000);
  });
}