在不订阅的情况下从Observable获取当前值(只需要一次值)



如何在不订阅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提供了一个名为valuegetter属性,以获取通过它传递的最新值


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);
});
}

相关内容