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

Eri*_*icC 34 observable rxjs angular

标题说明了一切.如何在不订阅的情况下从Observable获取当前值?我只想要当前值一次而不是新值,因为它们正在进入......

Ank*_*ngh 33

你需要使用BehaviorSubject,

  • BehaviorSubject类似于ReplaySubject,只是它只记住最后一个发布.
  • BehaviorSubject还要求您为其提供默认值T.这意味着所有订阅者将立即收到一个值(除非它已经完成).

它将为您提供Observable发布的最新值.

BehaviorSubject提供了一个getter名为的属性,value用于获取通过它传递的最新值.


PLUNKER DEMO

  • 在此示例中,值'a'将写入控制台:

  //Declare a Subject, you'll need to provide a default value.
  var subject: BehaviorSubject<string> = new BehaviorSubject("a");
Run Code Online (Sandbox Code Playgroud)

用法:

console.log(subject.value); // will print the current value
Run Code Online (Sandbox Code Playgroud)

  • 但是BehaviorSubject具有'next'方法,因此如果一个类向其客户端公开BehaviorSubject,那么这些客户端就能够触发'next'并更新变量.所需要的是一种将源对象中的BehaviorSubject的值和Observable成员公开给其客户端的方法.有谁知道我怎么做到这一点? (5认同)

Sim*_*ver 8

快速回答:

...我只希望当前值一次而不是新值,因为它们即将出现...

您仍将使用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);
Run Code Online (Sandbox Code Playgroud)

重要的是要注意,上面的订阅调用不会等待值。如果没有立即可用的订阅函数,则永远不会调用该函数,而我故意将取消订阅的调用放在那儿,以防止其“稍后出现”。

因此,这不仅显得笨拙-不适用于无法立即使用的内容(例如来自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!');
   }
}
Run Code Online (Sandbox Code Playgroud)

那为什么这么糟糕

  • 如果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!');
      }

   });
 }
Run Code Online (Sandbox Code Playgroud)

上面的代码创建了一个新的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()));
Run Code Online (Sandbox Code Playgroud)

请注意以下几点:

  • 在绝对需要之前我不订阅-在这种情况下,该功能不在功能范围内
  • “行驶”可观测值是zooOpen$,它用于switchMap“切换”到另一个可观测值,该观测值最终是从返回的getZooReport()
  • 如果zooOpen$改变的话,它取消一切并在第一个内部重新开始switchMap。阅读有关switchMap的更多信息。
  • 注意:里面的代码switchMap必须返回一个新的observable。您可以使用of('hello')- 快速创建一个或返回另一个可观察到的值,例如combineLatest
  • 同样:map必须只返回一个常规字符串。

很快,我开始刻意不要订阅,直到突然不得不开始编写更加高效,灵活,简洁和可维护的代码。

另一个最后的注意事项:如果将这种方法与Angular一起使用,则可以subscribe通过使用| async管道获得上述Zoo报告而没有一个。这是实践中“直到您必须订阅”主体的一个很好的例子。

// in your angular .ts file for a component
const zooReport$ = this.getZooReport();
Run Code Online (Sandbox Code Playgroud)

并在您的模板中:

<pre> {{ zooReport$ | async }} </pre>
Run Code Online (Sandbox Code Playgroud)

另请参阅我的回答:

/sf/answers/3794699961/

上面也没有提到以避免混淆:

  • tap()有时可能对“从可观察到的价值中获取价值”很有用。如果您不熟悉该运算符,请仔细阅读。RxJS使用“管道”,而tap()运算符是“挖掘管道”以查看其中的一种方法。

  • 这非常清楚,这是我所见过的对此问题的最佳答案。对此表示感谢,谢谢! (2认同)

小智 8

const value = await this.observableMethod().toPromise();