无需订阅即可从 Observable 获取当前值(只需要一次值)

Get current value from Observable without subscribing (just want value one time)

如何在不订阅 Observable 的情况下获取当前值?我只想要一次当前值,而不是新值。

您需要使用BehaviorSubject,

  • BehaviorSubject is similar to ReplaySubject except it only remembers the last publication.
  • BehaviorSubject also requires you to provide it a default value of T. This means that all subscribers will receive a value immediately (unless it is already completed).

它将为您提供 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;
  }
}

不确定这是否是您要查找的内容。可能在服务中写那个行为主题。将其声明为私有并仅公开您设置的值。像这样

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

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

这样外部用户只能订阅 behaviorSubject 并且您可以在服务中设置所需的值。

快速回答:

...I just want the current value one time and not new values as they are coming in...

您仍将使用 subscribe,但使用 pipe(take(1)),因此它会为您提供一个值。

例如。 myObs$.pipe(take(1)).subscribe(value => alert(value));

另请参阅:

之间的比较

更长的答案:

一般规则是你应该只从具有 subscribe()

的可观察值中获取值

(或异步管道,如果使用 Angular)

BehaviorSubject 绝对有它的位置,当我开始使用 RxJS 时,我经常使用 bs.value() 来得到一个值。随着你的 RxJS 流在你的整个应用程序中传播(这就是你想要的!),那么这样做会变得越来越难。通常你会看到 .asObservable() 习惯于 'hide' 底层类型 防止 某人使用 .value() - 起初这看起来很刻薄,但你会开始明白为什么它会随着时间的推移而完成。此外,您迟早会需要 不是 BehaviorSubject 的值,而且没有办法做到这一点。

回到最初的问题。特别是如果你不想 'cheat' 使用 BehaviorSubject.

更好的方法是始终使用 subscribe 来获取值。

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

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

这两者之间的区别是 first() 如果流已经完成 将出错 并且如果流已经完成 take(1) 将不会发出任何可观察值或者没有立即可用的值。

注意:这被认为是更好的做法,即使您使用的是 BehaviorSubject。

但是,如果您尝试上面的代码,可观察对象的 'value' 将在订阅函数的闭包内 'stuck',您可能在当前范围内需要它。如果你真的需要解决这个问题,一种方法是:

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

重要的是要注意上面的订阅调用不会等待一个值。如果没有立即可用的订阅函数将永远不会被调用,我故意将取消订阅调用放在那里以防止它 'appearing later'.

所以这不仅看起来非常笨拙 - 它不适用于不能立即获得的东西,比如来自 http 调用的结果值,但它实际上可以用于行为主题(或更多重要的是 上游并且已知为 BehaviorSubject* 或采用两个 BehaviorSubject 值的 combineLatest 的东西)。绝对不要去做 (obs$ as BehaviorSubject)- 呃!

这个前面的例子总体上仍然被认为是一种不好的做法——一团糟。如果我想查看一个值是否立即可用并且能够检测它是否可用,我只会使用以前的代码样式。

最佳方法

如果你能尽可能长时间地保持所有东西都是可观察的 - 并且只在你绝对需要这个值时订阅 - 而不是试图 'extract' 一个值到包含范围这就是我在上面所做的。

例如。比方说,如果您的动物园开放,我们想报告我们的动物。您可能认为您想要 zooOpen$ 的 'extracted' 值,如下所示:

方法不对

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 observable!
  • ,你永远不会用上面的代码得到一个值
  • 如果你想使用这个报告怎么办'outside'这个功能。您现在已将 alert 锁定到此方法中。如果您必须在其他地方使用该报告,则必须复制它!

好办法

与其尝试访问函数中的值,不如考虑创建一个新的 Observable 并且甚至不订阅它的函数!

它 return 是一个可以被消耗的新的可观察对象 'outside'。

通过将所有内容保持为可观察值并使用 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()));

注意以下几点:

  • 除非我绝对需要,否则我不会订阅 - 在这种情况下,这超出了功能
  • 'driving' observable 是 zooOpen$,它使用 switchMap 到 'switch' 到不同的 observable,最终是 return 从 getZooReport().
  • 如果 zooOpen$ 发生变化,它会取消所有内容并在第一个 switchMap 中重新开始。阅读有关 switchMap 的更多信息。
  • 注意:switchMap 中的代码必须 return 一个新的可观察对象。您可以使用 of('hello') 或 return 另一个可观察对象(例如 combineLatest.
  • 快速创建一个
  • 同样:map 必须只是 return 一个常规字符串。

当我开始记下在我 之前不要订阅时,我突然开始编写更高效、更灵活、更清洁和可维护的代码。

另一个最后的注意事项:如果您将此方法与 Angular 一起使用,则可以通过使用 | async 管道获得上述动物园报告而无需单个 subscribe。这是实践中“在必须订阅之前不要订阅”原则的一个很好的例子。

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

并在您的模板中:

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

另请参阅我的回答:

为了避免混淆,上面也没有提到:

  • tap() 有时可能对 'get the value out of an observable' 有用。如果您不熟悉该运算符,请阅读它。 RxJS 使用 'pipes' 和 tap() 运算符是一种 'tap into the pipe' 查看那里有什么的方法。

使用 .toPromise() / async

参见 https://benlesh.medium.com/rxjs-observable-interop-with-promises-and-async-await-bebb05306875

中的 'Use toPromise() with async/await to emit the last Observable value as a Promise'

使用 Observable 构造函数创建任何类型的可观察流。当可观察对象的 subscribe() 方法执行时,构造函数将 运行 的订阅函数作为其参数。订阅者函数接收一个观察者对象,并可以将值发布到观察者的 next() method.Try this

@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);
  });
}
const value = await this.observableMethod().toPromise();