【问题标题】:RxJS "unsubsribe" method in ngOnDestroy does not disposes resources quickly enoughngOnDestroy 中的 RxJS“取消订阅”方法没有足够快地处理资源
【发布时间】:2020-10-06 11:53:11
【问题描述】:

我正在调查一个问题,该问题似乎源于“订阅”类的“取消订阅”方法似乎没有足够快地处理资源,从而导致内存泄漏。

这是我的场景:

我有 2 个组件 - LandingPageComponent (LPC) 和 WebPlayerComponent (WPC)。 LPC 是用户在访问站点时看到的第一页。在该页面上,他们有一个链接将他们带到第二页或 WPC(传递一个参数,例如“Operator Benchmarking”)用户通过单击浏览器的后退按钮返回上一页。这会触发 WPC 的 ngOnDestroy 方法处理订阅(参见下面的代码)。

WebPlayer组件

export class WebPlayerComponent implements OnInit, OnDestroy {
    @Input() workbookId: string;
    @Input() workbookPage: string | number;

    private _workbooks: Workbook[];
    private _filters: Filter[];

    private _subscriptions: { [key: string]: Subscription } = {};
    
    ngOnInit() {
        this._subscriptions["combined"] = combineLatest(
            this.workbookService.workbooks$,
            this.filterService.filters$,
            this.libraryService.userFolderInitialised$
        ).subscribe(([workbooks, filters, userFolderInitialised]) => {
            this._workbooks = workbooks;
            this._filters = filters;

            this._subscriptions["webPlayerServiceSubscription"] = this.webPlayerService.openWorkbook(workbook.libraryPath, parameterString).subscribe(
                    (webPlayer) => {
                        console.log("_subscriptions before 'openWorkbook call'");
                        Object.keys(this._subscriptions).map((key) => {
                        console.log(key);
                        });

                        console.log("Openning document page: " + this.workbookPage);
                        webPlayer.openDocument("spotfire-container", this.workbookPage);
                    });
        });

    }
    
    ngOnDestroy() {
        Object.keys(this._subscriptions).map((key) => {
            this._subscriptions[key].unsubscribe();
            console.log("---Unsubscribed " + key + "---");
        });
    }
}

如果用户单击浏览器的后退按钮并足够快地单击“链接”(在我的示例中不到 5 秒),就会出现问题。应该被处理的订阅不是并且仍然是活动的。我可以通过查看控制台的输出来确认:

在上图中,您可以看到再次从 LandingPageComponent 中单击“链接”后,我们看到了 2 组调用(绿色数字 (1) 和 (2)),我们还查看“打开文档页面:” 3 次

如果我在再次单击链接之前等待 5 秒,输出应该是这样的

我不确定为什么我的订阅资源处理得不够快(至少我认为这是问题所在)。

注意:我使用的是 RxJS 6.4.0Angular 8.1.3

【问题讨论】:

  • 您能否尝试一次将您的订阅(仅用于测试目的)添加到订阅类。 subs = new Subscription(),然后将您的订阅添加到subs,方法是:subs.add(observable$.subscribe(...))。在取消订阅中,只需拨打subs.unsubscribe()。如果这解决了您的问题,请通知我们。然后我们知道它是因为订阅对象。
  • 我建议尝试:1. 使用 takeUntil 运算符取消订阅,2. 删除嵌套订阅 - 所以在 this.webPlayerService.openWorkbook 调用中使用 switchMap 运算符
  • 现在我看到了这个问题。感谢@vitaliy kotovs 的评论。我实际上没有看到您在订阅中拥有订阅。内存泄漏的原因是,每当外部订阅发出时,您都会覆盖对旧订阅的引用。这意味着您只需取消订阅最后添加的订阅。前一个将永远留在记忆中并工作。
  • 感谢两位的建议。 @vitaliykotov 我过去确实尝试过takeUntil,但没有运气。至于使用switchMap 运算符,我不确定如何在我的用例中应用它。具体来说,不确定switchMap(<here> "this._subscriptions["webPlayerServiceSubscription"] = this.webPlayerService.openWorkbook(workbook.libraryPath, parameterString).pipe(switchMap()).subscribe(..." @JonathanStellwag这是否意味着您使用单个 subs 订阅的建议在我的情况下不起作用?

标签: angular rxjs rxjs6 rxjs-observables


【解决方案1】:

您可以使用switchMap 喜欢:

ngOnInit() {
    this._subscriptions["combined"] = combineLatest(
            this.workbookService.workbooks$,
            this.filterService.filters$,
            this.libraryService.userFolderInitialised$
    )
    .pipe(
        switchMap(([workbooks, filters, userFolderInitialised]) => {
            this._workbooks = workbooks;
            this._filters = filters;

            return this.webPlayerService.openWorkbook(workbook.libraryPath, parameterString)
        })
    )
    .subscribe((webPlayer) => {
        webPlayer.openDocument("spotfire-container", this.workbookPage);
     });
    }

因此,您将 observable 从 combineLatest 更改为 openWorkbook

【讨论】:

  • 谢谢你,维塔利。非常感谢,这就像一个魅力。我还是 RxJS 的新手,你的输入(连同 Jonathan Stellwag 的输入)在这里学到了一个教训——不要嵌套订阅。谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-03-14
  • 1970-01-01
  • 2018-05-27
  • 2019-02-04
  • 2021-01-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多