В названии говорится, что все действительно. Как я могу получить текущее значение из Observable, не подписавшись на него? Я просто хочу, чтобы текущее значение было одно время, а не новые значения, когда они входят...
Ответ 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()
- это способ "подключиться к каналу", чтобы увидеть, что там.