【问题标题】:rxjs: Subscriber does not fire on all changes in different classrxjs:订阅者不会触发不同类中的所有更改
【发布时间】:2020-05-19 09:31:58
【问题描述】:

我有一个具有 BehaviorSubject 和 Observable 的 js 类。我想在不同的类中订阅 Observable,但它不能正常工作。

//class One: 
export default ClassOne {
     constructor(){
         this._store = new BehaviorSubject({}); 
         this.store = this._store.asObservable(); 
         this.store.subscribe(data => {console.log(data)}) //logs the data, no Problem here
         //{};{ test: { foo: "bar", max: "muster" } };{ test: { foo: "bar", max: "muster" } };...
     }
     addData(data){ this._store.next(data)} //iscalled a few times.

     getStore () {return this.store}        //using a getter Function does not work either

}

//class Two
import class1 from "./class1"; 
ClassTwo {
    constructor(){
        this.one = new ClassOne(); 
        this.one.store.subscribe(data =>{console.log(data)}) //only logs {} once. Is never fired again. 
        this.one.getStore().subscribe(data =>{console.log(data)}) //Same Problem
    }
}

所以我的主要问题是:如何确保订阅者获得 ClassTwo 中的所有更改?

请注意,Observable 已定义并触发一次,但不会收到新更改的通知。

ClassOne 是 Singleton 时有什么不同吗?:

  //class One: 
instance = null; 
export default ClassOne {
     constructor(){
         if (instance === null) {
             instance = this;
         }
         instance._store = new BehaviorSubject({}); 
         instance.store = this._store.asObservable(); 
         instance.store.subscribe(data => {console.log(data)}) //logs the data, no Problem here
     }
     addData(data){ instance._store.next(data)} //iscalled a few times. 
     getStore () {return instance.store}        //using a getter Function does not work either
}

编辑:测试是否为单例 (开玩笑)

beforeAll(() => {
   one = new ClassOne();
});
test("Singleton Test", () => {
  let one2 = new ClassOne();
  expect(one2 instanceof ClassOne).toEqual(true);
  expect(one2).toEqual(one);
  expect(one2 === one).toEqual(true);
});

Edit2:添加数据的使用

beforeAll(() => {

one = new ClassOne();
two = new ClassTwo(); 
});
  test("", (done) => {
    one.addData({ test: { foo: "bar", max: "muster" } });
    one.addData({ test: { foo: "bar", max: "muster" } });
    one.addData({ test: { foo: "bar", max: "muster" } });        

    //I just coded this by heart, but the test is somthing like this
    expect(one.getStore()).toEqual(two.one.store);

    //makes sure that the subscriber have enough time
    setTimeout(() => done(), 5000);
  }, 6000);

Edit3:使用 share/shareReplay

我已经更改了 getStore() 函数:

//ClassOne
getStore(){ return instance.store.pipe(share()) }
//and I tried: 
getStore(){ return instance.store.pipe(shareReplay()) }

//ClassTwo: 
this.one.getStore().subscribe(data =>{console.log(data)}) 

但问题仍然存在。

Edit4:一些故障排除

所以经过一些测试和阅读后,我得出结论,构造函数中的订阅无法正常工作。

虽然这不起作用:

   let two = new ClassTwo(); //with the subscription on the store

这行得通:

   let two = new ClassTwo(); //without the subscription on the store
   two.subscribeToMyStore(); 

   //subscribeToMyStore: 
   subscribeToMyStore(){
       this.one.store.subscribe(data =>{console.log(data)})
   }

那么为什么我不能在构造函数中进行订阅呢?

为什么它会订阅 ClassOne 的构造函数而不订阅 ClassTwo?

【问题讨论】:

  • 它按我的预期工作。 "this.one.store.subscribe(data =>{console.log(data)}) // 只记录一次 {}。永远不会再次触发。"这是正确的,因为您使用的是 BehaviorSubject。只要调用 instanceOfClassTwo.one 的 addData,它就会记录两次新值。
  • 顺便说一句,如果你使用 Angular,那么 ClassOne 的代码似乎可以成为服务。
  • 我没有使用角度。我正在创建一个 npm 包,其类与所描述的类相似。
  • 您想在不同对象之间共享同一个 ClassOne 实例吗?
  • 是的,我愿意。它应该是某种商店。

标签: javascript rxjs


【解决方案1】:

您必须重复使用相同的实例。要么确保它是单例并使用一些 DI 框架通过构造函数注入,要么自己从单独的模块中导出单例,不要忘记share 它。请注意,当需要shareReplay 时,存在相当流行的场景。

【讨论】:

  • 我添加了一个笑话测试(为问题重写)以表明它是一个单例。所以实例应该是一样的。
  • ClassOne 中的订阅者也仅用于测试目的。我最初的问题是 ClassTwo 中的订阅者没有得到更改。
  • 所以分享你的意思是这样的?:getStore () {return this.store.pipe(share())} 无论如何感谢分享的提示。我会检查它们,但需要一些时间将它们应用到我的实际项目中。
  • 所以我已经尝试过了,但它不起作用。它仍然很有用,因为我计划申请多个订阅者。将很快扩展问题中的代码。
  • 我不确定测试如何解决signleton,它们是否保留相同的内存或创建新实例;但它肯定会在常规运行时工作,经过多次测试。查看 Angular 文档以获取更多高级 DI 与 RXJS 示例。
【解决方案2】:

所以问题在于 Jest 的速度。 Jest 在执行 classTwo 订阅中的日志之前完成并清理了所有内容。

虽然很奇怪,但对我有用的解决方法是在我的测试中触发完成事件,并且只在完成回调中调用 done()。看起来像这样:

 beforeAll(() => {
    one = new ClassOne();
    two = new ClassTwo(); //subcription in constructor
 });
 test("", (done) => {
    one.addData({ test: { foo: "bar", max: "muster" } });
    one.addData({ test: { foo: "bar", max: "muster" } });
    one.addData({ test: { foo: "bar", max: "muster" } });        

    //I just coded this by heart, but the test is something like this
    two.one.store.subscribe({
        complete() {
            //Test the final state of everything here
            //Only call done() here. 
            done();
        },
    });
    setTimeout(
        () => one.store.complete(),   //call complete after a timeout
        1000
    );
 }, 2000);

所以我的外卖在这里:

  • 在构造函数中订阅有效。
  • 确保测试环境在收到更改之前不会破坏 Obeservable 的底层实例。
  • 在 setTimeOut 中对 (Behavior)Subject 调用完成,并在完成 () 的回调中调用 done()。
  • 为什么 classTwo 订阅这么慢,数据只在 classOne 接收,我仍然无法理解。

【讨论】:

    猜你喜欢
    • 2023-03-12
    • 1970-01-01
    • 2021-12-22
    • 1970-01-01
    • 2022-07-11
    • 1970-01-01
    • 1970-01-01
    • 2019-04-03
    • 2018-06-15
    相关资源
    最近更新 更多