【问题标题】:Get current value from Observable without subscribing (just want value one time)无需订阅即可从 Observable 获取当前值(只需要一次值)
【发布时间】:2016-09-17 06:23:09
【问题描述】:

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

【问题讨论】:

  • 你想在使用http请求时直接从.map获取数据?
  • 实际上不,我有自己的 Observable(应用程序的状态),通常我使用订阅并在更改进入时对其进行操作,但现在我只需要获取当前状态(未来没有任何更新的变化)...
  • 嗯,看起来take方法返回了一个Observable,我只想要值(在我的例子中是字符串)...
  • 你可以在这里查看答案:stackoverflow.com/a/54387211/10974432

标签: angular rxjs observable


【解决方案1】:

快速回答:

...我只想要一次当前值,而不是新值,因为它们即将到来...

您仍将使用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 发出”

【讨论】:

  • 这非常清楚,是我见过的这个问题的最佳答案。对此表示敬意,谢谢!
  • @Shem ha 谢谢。我确实对这个答案有点忘乎所以!
  • 这种情况下还需要退订吗?
  • @tobias 如果您立即获得价值并使用take(1),则永远不需要取消订阅。但通常应该避免这种方法 - 除非您知道 100% 的值是立即可用的。
  • 我现在没有时间更新问题,但您也可以在异步函数中使用awaitindepth.dev/rxjs-for-await-what
【解决方案2】:
const value = await this.observableMethod().toPromise();

【讨论】:

  • 这实际上是适合我的方法。
  • 这是一篇关于 promises + RxJS 的好文章:benlesh.medium.com/… - 见Use toPromise() with async/await to emit the last Observable value as a Promise部分
【解决方案3】:

你需要使用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;
  }
}

【讨论】:

  • 我有这样的状态(使用 TypeScript):myState:EventEmitter&lt;string&gt; = new EventEmitter();,所以我必须使用 BehaviorSubject 而不是 EventEmitter,对吗?类似myState:BehaviorSubject&lt;string&gt; = new BehaviorSubject('default state');
  • 是的,没错。 EventEmitter 使用简单的 Observable。
  • 但是 BehaviorSubject 有 'next' 方法,所以如果一个类向其客户端公开一个 BehaviorSubject ,那么这些客户端就有能力触发 'next' 并更新变量。所需要的是将源对象中的 BehaviorSubject 的值和 Observable 成员仅公开给其客户的方法。有谁知道我该怎么做?
  • @Neutrino:使用 TypeScript,您可以将 BehaviorSubject&lt;T&gt; 转换为 Observable&lt;T&gt; 并且只将那个 observable 暴露给其他人。你会好吗?
  • 我不确定这真的能取得多大的成就。任何人所要做的就是将转换对象分配给一个无类型的变量,他们仍然可以在它上面调用'next',不是吗?
【解决方案4】:

使用 Observable 构造函数创建任何类型的可观察流。当 observable 的 subscribe() 方法执行时,构造函数将订阅者函数作为其参数。订阅者函数接收一个 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);
  });
}

【讨论】:

  • 虽然此代码可能会回答问题,但提供有关此代码为何和/或如何回答问题的额外上下文可提高其长期价值。
  • 您还有什么想要的吗? @adiga
【解决方案5】:

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

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

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

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

【讨论】:

    猜你喜欢
    • 2020-04-05
    • 1970-01-01
    • 1970-01-01
    • 2021-10-15
    • 1970-01-01
    • 2020-09-18
    • 2014-03-07
    • 2019-10-22
    • 1970-01-01
    相关资源
    最近更新 更多