快速回答:
...我只想要一次当前值,而不是新值,因为它们即将到来...
您仍将使用subscribe,但使用pipe(take(1)),因此它为您提供一个单一的值。
例如。 myObs$.pipe(take(1)).subscribe(value => alert(value));
另见:first(), take(1) or single()之间的比较
更长的答案:
一般规则是你应该只从带有 subscribe() 的 observable 中获取一个值
(如果使用 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 的“值”将“卡”在 subscribe 函数的闭包中,您很可能在当前范围内需要它。如果你真的需要,解决这个问题的一种方法是:
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)- ugh!
一般来说,前面的示例仍然被认为是一种不好的做法 - 它一团糟。如果我想查看一个值是否立即可用并且能够检测它是否不可用,我只会使用以前的代码样式。
最佳方法
如果您可以尽可能长时间地将所有内容保持为可观察的 - 并且仅在您绝对需要该值时订阅 - 而不是尝试将值“提取”到包含范围中,那么您会更好上面做的。
例如。假设我们想对我们的动物进行报告,如果您的动物园是开放的。您可能会认为您想要 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 并且甚至不订阅它的函数!
相反,它返回一个可以在“外部”使用的新 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!');
}
});
}
上面的创建了一个新的 observable,所以我们可以在其他地方运行它,如果我们愿意的话,还可以通过管道进行更多的操作。
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()));
注意以下几点:
- 除非绝对需要,否则我不会订阅 - 在这种情况下,它不在功能范围内
- “正在驾驶”的 observable 是
zooOpen$,它使用 switchMap 来“切换”到不同的 observable,最终是从 getZooReport() 返回的那个。
- 如果
zooOpen$ 发生变化,它的工作方式将取消所有内容并在第一个switchMap 内重新开始。阅读 switchMap 了解更多信息。
- 注意:
switchMap 中的代码必须返回一个新的 observable。您可以使用 of('hello') 快速创建一个 - 或返回另一个 observable,例如 combineLatest。
- 同样:
map 必须只返回一个常规字符串。
当我开始在心里记下在我不得不订阅之前不要订阅时,我突然开始编写更高效、更灵活、更简洁和可维护的代码。
最后一点:如果您在 Angular 中使用这种方法,您可以通过使用 | async 管道获得上述动物园报告而无需单个 subscribe。这是实践中“必须先不要订阅”原则的一个很好的例子。
// 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() / 异步
请参阅https://benlesh.medium.com/rxjs-observable-interop-with-promises-and-async-await-bebb05306875 中的“使用 toPromise() 和 async/await 将最后一个 Observable 值作为 Promise 发出”