【问题标题】:Is it ok to have a side effect event emission in RxJS pipe?在 RxJS 管道中有副作用事件发射可以吗?
【发布时间】:2022-01-19 19:18:43
【问题描述】:

我在 Angular 应用程序的上下文中使用 RxJS。

我有一项服务可以根据需要使用不同的设置重新初始化整个应用程序

@Injectable()
class BootstrapService{

 public initApplication(config:appConfig):Observable<boolean>{
        return somehowCreatedObservable;
 };
}

预期用途如下

{
 
///.... code in part of application that allows user to reinit app

this.bootstrapService.initApplication({someconfig}).subscribe(result=>{
  if(result){
      //report success
   }else report failure
}
}

所以这就像在 http 调用中一样可以冷观察。

现在,我需要向一些相关组件发送额外的通知(例如重建用户菜单、工具栏等 - 以反映从服务器加载的新配置)。

我想通过在BoostrapService#initApplicaiton 内的管道末尾使用主题添加额外的事件发射来做到这一点,就像这样

 public initApplication(config:appConfig):Observable<boolean>{
       return somehowCreatedObservable().pipe(tap(result=>if(result)this.subject.next(someEvent))
 };

然而,这是一个副作用(如果我没记错的话),而函数式编程不应该避免这样的结构,据我了解可以在互联网上找到关于这个主题的内容。

那么问题是,是否可以将此类事件作为副作用发出,还是应该以不同的方式解决?

另一个我想到但仍然不正确的解决方案是让我的操作成为一个“热”的可观察对象,不会返回给调用者,而是使用公共流,例如

 appInitResult:Subject<boolean>
 public initApplication(config:appConfig):Observable<boolean>{
      somehowCreatedObservable().subscribe(r->this.appInitResult.next(r));
  return this.appInitResult.asObservable();
 };

所以一切都可以订阅完全相同的流,包括方法调用者。

【问题讨论】:

    标签: angular typescript rxjs


    【解决方案1】:

    虽然应该避免外部副作用,但它们有时是不可避免的。而且你实现这种副作用的方式是正确的。

    话虽如此,我的第一个问题是什么场景需要您重新初始化应用程序?

    在登录用户的示例中,您可以设置应用的响应式数据流,其中用户可观察到的位于该流的顶部。这样,每当用户变量发出一个新用户时,所有可观察对象都会对该值做出反应,并根据用户对象发出自己的更新值。

    设置它可能有点令人生畏,因为它需要您在任何地方使用 observables。

    【讨论】:

    • 要回答为什么,我可以透露应用程序是模型驱动的,并且允许用户在运行时更改模型而无需重新加载应用程序 - bootstrapService.initApplication 是 APP_INITIALIZER 的一部分,可以通过以下方式调用用户。
    • 我还在考虑将 subject 作为 Observable 从 initApplication 返回,因此不仅调用者会收到通知,所有相关方也会收到通知 - 使用单一事实来源 - 但应用程序重新初始化不会更长的“冷”过程,因为我必须立即订阅它,并在隐藏的subscribe 中发送主题。这将摆脱 tap 运算符,但对我来说仍然是一种代码味道。
    【解决方案2】:

    看起来你正在使用 Angular,在这种情况下,让我试着给你我的意见。

    如果我理解正确,您有一个应用程序,其中许多组件对同一事件流感兴趣。

    如果是这种情况,我要做的是创建一个 myService 来公开 2 个 API:

    • 一个公共的 Observable 通知组件感兴趣的事件
    • 允许 myService 的外部客户端触发事件通知的公共方法

    换句话说,代码是

    @Injectable()
    class MyService{
     private _myEvent = new Subject<any>()
     public myEvent = this._myEvent.asObservable()
    
     public notifyEvent(event: any) {
       this._myEvent.next(event)
     };
    }
    

    现在任何想要使用myService的组件或服务都可以通过依赖注入获取并使用它。

    例如BootstrapService 会是这样的

    @Injectable() 类引导服务{ 构造函数(私有 myService:MyService){}

    公共 initApplication(config:appConfig) { // 做任何需要的事情来创建事件 常量 _event = buildEvent(config) this.myService.notifyEvent(事件) }; }

    任何其他需要通知的组件都会这样

    @Injectable()
    class MyComponent{
     constructor(private myService: MyService) {}
     
     // use myService.myEvent as required, e.g. subscribing to it in ngOnInit
     public ngOnInit() { 
            const _event = buildEvent(config)
            this.myService.myEvent.subscribe({
              next: event => {
                 // do whatever with the event
              }
            })
     };
    }
    

    这样构建的组件也可以通过async 管道直接在模板中使用myService.myEvent,如果唯一要做的事情是显示事件中包含的内容,这是建议的方法。

    您可能会发现this article 很有趣。它谈到了 React,但服务的概念与我在上面试图描述的概念相同。

    最后,关于你的评论“函数式编程应该避免副作用”,我认为必须考虑一下。

    函数式编程无法避免副作用。如果任何程序想要做一些有用的事情,例如,它必须有副作用。写一些东西或展示一些东西。重要的是隔离副作用,这就是函数式编程可以提供帮助的地方:它有助于隔离副作用。

    在这种情况下,副作用在subscribe 逻辑中被隔离。

    【讨论】:

    • 这在我看来就像我所描述的(可能很糟糕)的 1:1 方法:有共同的主题并用它发出事件。但是,我的疑问是如何将事件从一个流传播到另一个流。在您的示例中,看起来调用者必须记住发出事件,例如bootstrapService.ini().subscribe(()-&gt;eventsService.emit(),我认为这是应该避免的。这就是为什么我希望服务在使用了它的方法后广播通知的原因,因为它可以保证每当初始化完成时,都会发出事件。
    • 您可以在eventsService 中重复使用ini Observable/Stream,无需发射到新的Subject。 Observables 建立在组合的概念之上。
    • somehowCreatedObservable 实际上在做什么?它是否创建了一个 Observable,在订阅时执行 http 调用来读取配置?
    猜你喜欢
    • 2020-07-11
    • 1970-01-01
    • 2020-10-24
    • 1970-01-01
    • 2018-11-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多