什么时候在 rxjs 中使用 asObservable()?

When to use asObservable() in rxjs?

我想知道asObservable有什么用:

根据文档:

An observable sequence that hides the identity of the source sequence.

但是为什么需要隐藏序列?

何时使用 Subject.prototype.asObservable()

这样做的目的是防止从 API 中泄露主题的“观察者端”。基本上是为了防止在您不希望人们能够“下一步”进入结果可观察对象时出现泄漏抽象。

例子

(注意:这真的不是你应该如何将这样的数据源变成 Observable,而是你应该使用 new Observable 构造函数,见下文)。

const myAPI = {
  getData: () => {
    const subject = new Subject();
    const source = new SomeWeirdDataSource();
    source.onMessage = (data) => subject.next({ type: 'message', data });
    source.onOtherMessage = (data) => subject.next({ type: 'othermessage', data });
    return subject.asObservable();
  }
};

现在,当有人从 myAPI.getData() 中获得可观察的结果时,他们无法 next 将值输入到结果中:

const result = myAPI.getData();
result.next('LOL hax!'); // throws an error because `next` doesn't exist

你通常应该使用 new Observable(),不过

在上面的示例中,我们可能正在创建我们并非有意创建的内容。一方面,getData() 不像大多数可观察对象那样惰性,它会立即创建基础数据源 SomeWeirdDataSource(可能还有一些副作用)。这也意味着,如果您 retryrepeat 结果可观察值,它不会像您认为的那样工作。

最好将数据源的创建封装在您的可观察对象中,如下所示:

const myAPI = {
  getData: () => return new Observable(subscriber => {
    const source = new SomeWeirdDataSource();
    source.onMessage = (data) => subscriber.next({ type: 'message', data });
    source.onOtherMessage = (data) => subscriber.next({ type: 'othermessage', data });
    return () => {
      // Even better, now we can tear down the data source for cancellation!
      source.destroy();
    };
  });
}

使用上面的代码,可以使用 RxJS 的现有运算符在可观察对象之上组合任何行为,包括使其“不惰性”。

A Subject 可以同时充当 observerobservable.

一个Obervable有2个方法。

  • 订阅
  • 退订

只要您订阅 observable,您就会收到 observer 上面有 nexterrorcomplete 方法。

您需要隐藏序列,因为您不希望流源在每个组件中都公开可用。同样可以参考@BenLesh的例子

P.S。 : 第一次来ReactiveJavascript的时候,看不懂asObservable。因为我必须确保我清楚地了解基础知识,然后再去 asObservable。 :)

除了 我还要提一下,我认为这取决于使用的语言。

对于像 JavaScript 这样的无类型(或弱类型)语言,通过创建像 [=11 这样的委托对象来向调用者隐藏源对象可能是有意义的=] 方法。尽管如果您考虑一下,它不会阻止调用者执行 observable.source.next(...)。所以这种技术并不能防止 Subject API 泄漏,但它确实使它对调用者更加隐藏。

另一方面,对于像 TypeScript 这样的强类型语言,方法 asObservable() 似乎没有多大意义(如果有的话)。 静态类型语言通过简单地利用类型系统(例如接口)解决了 API 泄漏问题。例如,如果你的 getData() 方法被定义为 returning Observable<T> 那么你可以安全地 return 原来的 Subject,调用者会得到一个编译错误如果尝试在其上调用 getData().next()

想想这个修改过的例子:

let myAPI: { getData: () => Observable<any> }

myAPI = {
    getData: () => {
        const subject = new Subject()
        // ... stuff ...
        return subject
    }
}

myAPI.getData().next() // <--- error TS2339: Property 'next' does not exist on type 'Observable<any>'

当然,因为它在一天结束时全部编译为 JavaScript,所以在某些情况下您可能仍想创建委托。但我的观点是,这些情况的空间比使用 vanilla JavaScript 时要小得多,而且在大多数情况下你可能不需要这种方法。

(仅限 Typescript)使用类型而不是 asObservable()

我喜欢 关于使用类型的说法,所以我将添加一些额外的信息来澄清。


如果你使用asObservable(),那么你就是运行下面的代码

/**
 * Creates a new Observable with this Subject as the source. You can do this
 * to create customize Observer-side logic of the Subject and conceal it from
 * code that uses the Observable.
 * @return {Observable} Observable that the Subject casts to
 */
asObservable(): Observable<T> {
  const observable = new Observable<T>();
  (<any>observable).source = this;
  return observable;
}

这对 Javascript 很有用,但 在 Typescript 中不需要。我将在下面解释原因。


示例

export class ExampleViewModel {

   // I don't want the outside codeworld to be able to set this INPUT
   // so I'm going to make it private. This means it's scoped to this class
   // and only this class can set it.
   private _exampleData = new BehaviorSubject<ExampleData>(null);

   // I do however want the outside codeworld to be able to listen to
   // this data source as an OUTPUT. Therefore, I make it public so that
   // any code that has reference to this class can listen to this data
   // source, but can't write to it because of a type cast.
   // So I write this
   public exampleData$ = this._exampleData as Observable<ExampleData>;
   // and not this
   // public exampleData$ = this._exampleData.asObservable();
}

两者做同样的事情,但不会向您的程序添加额外的代码调用或内存分配。

this._exampleData.asObservable();
需要在运行时进行额外的内存分配和计算。

this._exampleData as Observable<ExampleData>;
由类型系统处理,不会在运行时添加额外的代码或内存分配。


结论

如果您的同事尝试这样做,referenceToExampleViewModel.exampleData$.next(new ExampleData());,那么它将在编译时被捕获并且类型系统不会让他们这样做,因为 exampleData$ 已被强制转换为 Observable<ExampleData>并且不再属于 BehaviorSubject<ExampleData> 类型,但他们将能够收听 (.subscribe()) 该数据源或扩展它 (.pipe())。

当您只希望特定服务或 class 设置信息源,而不希望人们随机设置该信息源时,这很有用 willy-nilly代码库中的点。