如何在不订阅Observable的情况下从Observable获取当前值?我只想要当前值一次,而不是新值,因为它们即将到来。
快速答案:
。。。我只想要当前值一次,而不是新值,因为它们即将到来…
您仍将使用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,这也被认为是更好的实践。
然而,如果您尝试上面的代码,则observable的"值"将被"卡住"在订阅函数的闭包中,并且您可能在当前范围中需要它。如果你真的必须这样做的话,有一种方法可以解决这个问题:
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*,或者取两个BehaviorSubject
值的combineLatest
)。绝对不要去做(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$
来自Web服务怎么办?前面的例子将如何运作?实际上,你的服务器有多快并不重要——如果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一起使用,则可以通过使用| async
管道在没有单个subscribe
的情况下获得上述zoo报告。这是一个很好的例子,说明了在实践中"不到万不得已就不要订阅"的原则。
// in your angular .ts file for a component
const zooReport$ = this.getZooReport();
并且在您的模板中:
<pre> {{ zooReport$ | async }} </pre>
另请参阅我的答案:
https://stackoverflow.com/a/54209999/16940
为了避免混淆,上面也没有提到:
tap()
有时可能有助于"从可观测值中获取值"。如果你不熟悉这个运算符,请阅读它。RxJS使用"管道",而tap()
运算符是一种"深入管道"查看内容的方法
带.toPromise()/async
请参阅中的"使用toPromise()with async/await将最后一个Observable值作为Promise发出"https://benlesh.medium.com/rxjs-observable-interop-with-promises-and-async-await-bebb05306875
带角度信号!(角度16+)
Angular 16推出了一个新的Signals功能预览。虽然功能尚未完成,但您已经可以开始在组件中使用信号了。设计目标之一正是为了解决这个"痛点"。
使用customer = toSignal(customer$, { requireSync: true })
,您可以创建一个信号,当可观察值发生变化时进行更新,并使用customer()
访问"立即"值,而无需上述常规样板。Angular会自动处理为实现此目的而创建的可观察对象的取消预订。
对此的思考:
- 使用
()
访问信号以获得其值是始终同步的,因此,如果您的可观测值没有值,您将遇到与上述相同的一些问题。这就是{ requireSync: true }
的作用(查看API文档了解更多信息-可能会更改) - 我并不是建议在需要现有可观测值的时候创建一个新的信号。这有点毫无意义!只有当您更新组件以部分或全部使用信号时,这种方法才会有用
- Signal API不是最终版本,所以请查看博客以获取最新信息
您需要使用BehaviorSubject,
- BehaviorSubject类似于ReplaySubject,只是它只记得上一次发布
- BehaviorSubject还要求您为其提供默认值T。这意味着所有订阅者都将立即收到一个值(除非已经完成)
它将为您提供Observable发布的最新值。
BehaviorSubject提供了一个名为value
的getter
属性,以获取通过它传递的最新值
StackBlitz
- 在本例中,值"a"被写入控制台:
//Declare a Subject, you'll need to provide a default value.
const subject: BehaviorSubject<string> = new BehaviorSubject("a");
用法:
console.log(subject.value); // will print the current value
隐藏主体,只暴露其价值
如果你想隐藏你的BehaviorSubject,只暴露它的值,比如说从服务,你可以使用这样的getter。
export class YourService {
private subject = new BehaviorSubject('random');
public get subjectValue() {
return this.subject.value;
}
}
const value = await this.observableMethod().toPromise();
ToPromise()在一段时间后被弃用,这里有一种新的rxjs方法:
let value = await lastValueFrom(someObservable);
或
let value = await firstValueFrom(someObservable);
如果你不确定里面已经有东西了,你可以使用:
let value = await lastValueFrom(someObservable, {defaultValue: "some value"})
https://indepth.dev/posts/1287/rxjs-heads-up-topromise-is-being-deprecated
不确定这是否是您想要的。可能在服务中编写该行为主题。将其声明为私有,并仅公开您设置的值。像这样的
@Injectable({
providedIn: 'root'
})
export class ConfigService {
constructor(private bname:BehaviorSubject<String>){
this.bname = new BehaviorSubject<String>("currentvalue");
}
getAsObservable(){
this.bname.asObservable();
}
}
通过这种方式,外部用户只能订阅behaviorSubject,您可以在服务中设置所需的值。
使用Observable构造函数创建任何类型的可观察流。构造函数将subscriber函数作为其参数,以便在observable的subscribe()方法执行时运行。subscriber函数接收一个Observer对象,并可以将值发布到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);
});
}