是否有必要取消订阅由Http方法创建的可观察对象?

bor*_*net 181 memory-leaks rxjs angular2-http angular

您是否需要取消订阅Angular 2 http调用以防止内存泄漏?

 fetchFilm(index) {
        var sub = this._http.get(`http://example.com`)
            .map(result => result.json())
            .map(json => {
                dispatch(this.receiveFilm(json));
            })
            .subscribe(e=>sub.unsubscribe());
            ...
Run Code Online (Sandbox Code Playgroud)

bor*_*net 228

所以答案是否定的,你没有.Ng2会自己清理它.

来自Angular的Http XHR后端源的Http服务源:

在此输入图像描述

注意它complete()在获得结果后如何运行.这意味着它实际上取消订阅完成.所以你不需要自己动手.

这是一个验证测试:

  fetchFilms() {
    return (dispatch) => {
        dispatch(this.requestFilms());

        let observer = this._http.get(`${BASE_URL}`)
            .map(result => result.json())
            .map(json => {
                dispatch(this.receiveFilms(json.results));
                dispatch(this.receiveNumberOfFilms(json.count));
                console.log("2 isUnsubscribed",observer.isUnsubscribed);
                window.setTimeout(() => {
                  console.log("3 isUnsubscribed",observer.isUnsubscribed);
                },10);
            })
            .subscribe();
        console.log("1 isUnsubscribed",observer.isUnsubscribed);
    };
}
Run Code Online (Sandbox Code Playgroud)

正如预期的那样,您可以看到在获得结果并完成可观察操作符后,它总是自动取消订阅.这发生在超时(#3),因此我们可以检查observable何时完成并完成.

结果

在此输入图像描述

因此,Ng2自动取消订阅不会存在任何泄漏!

很高兴提到:这Observable被归类为finite,infinite Observable与之相反,可以像DOM click侦听器一样发出无限的数据流.

谢谢@rubyboy对此有所帮助.

  • 在响应进入之前用户离开页面的情况下,或者在用户离开视图之前响应永远不会发生的情况会发生什么?那不会导致泄漏吗? (11认同)
  • 此处显示的示例涵盖了一个深角度私有 api。对于任何其他私有 api,它可以随升级而更改,恕不另行通知。经验法则是:如果没有正式记录,请不要做出任何假设。例如,AsynPipe 有明确的文档说明它会自动为您订阅/取消订阅。HttpClient 文档没有提及任何相关内容。 (8认同)
  • @ 1984答案总是取消订阅。正是由于此评论中提出的原因,这个答案是完全错误的。导航离开的用户是一个内存链接。另外,如果该订阅中的代码在用户离开后运行,则可能导致错误/产生意外的副作用。我倾向于在所有可观察对象上使用takeWhile(()=> this.componentActive),并在ngOnDestroy中将this.componentActive = false设置为清除组件中的所有可观察对象。 (2认同)

Sim*_*ver 67

你们有什么人在谈论!!!

好的,有两个理由可以取消订阅任何可观察的内容.似乎没有人谈论第二个!

1)清理资源.正如其他人所说,对于HTTP可观察量来说,这是一个微不足道的问题.它只是清理自己.

2)防止subscribe处理程序运行.(对于HTTP,这实际上也会在浏览器中取消请求 - 所以它不会浪费时间阅读响应.但这实际上是我的主要观点.)

数字2的相关性将取决于您的订阅处理程序的作用:

如果你的subscribe()处理函数有任何副作用,如果它关闭或处置的任何调用都是不希望的,那么你必须取消订阅(或添加条件逻辑)以防止它被执行.

考虑一些情况:

1)登录表格.输入用户名和密码,然后单击"登录".如果服务器很慢并且你决定点击Escape以关闭对话框怎么办?您可能会认为您没有登录,但是如果在您点击转义后返回了http请求,那么您仍将执行您在那里拥有的任何逻辑.这可能导致重定向到帐户页面,设置了不需要的登录cookie或令牌变量.这可能不是您的用户所期望的.

2)"发送电子邮件"表格.

如果subscribe"sendEmail" 的处理程序执行类似触发"您的电子邮件已发送"动画的操作,将您转移到其他页面或尝试访问已处置的任何内容,您可能会遇到异常或不需要的行为.

另外要注意不要假设unsubscribe()'取消'.一旦HTTP消息在飞行中,unsubscribe()如果已经到达您的服务器,则不会取消HTTP请求.它只会取消回复给您的回复.电子邮件可能会被发送.

如果你创建订阅电子邮件直接发送UI组件里面,那么你可能会想退订的处置,但如果邮件是由非UI集中服务发送的,那么你可能不会需要.

3)被破坏/关闭的Angular组件.除非您取消订阅,否则当时仍在运行的任何http observable都将完成并运行其逻辑onDestroy().后果是否微不足道将取决于您在订阅处理程序中执行的操作.如果您尝试更新不存在的内容,则可能会出错.

有时,如果组件被丢弃,您可能会有一些您想要的操作,有些则不会.例如,对于发送的电子邮件,您可能会发出'swoosh'声音.您可能希望即使组件已关闭也可以播放,但如果您尝试在组件上运行动画,则会失败.在这种情况下,订阅中的一些额外的条件逻辑将是解决方案 - 并且您不希望取消订阅http observable.

因此,在回答实际问题时,不需要这样做以避免内存泄漏.但是你需要(通常)这样做以避免因运行可能引发异常或破坏应用程序状态的代码而触发不必要的副作用.

提示:Subscription包含一个closed布尔属性,在高级情况下可能很有用.对于HTTP,它将在完成时设置.在Angular中,在某些情况下设置可由处理程序检查的_isDestroyed属性可能很有用.ngDestroysubscribe

提示2:如果处理多个订阅,您可以创建一个ad-hoc new Subscription()对象和add(...)任何其他订阅 - 所以当您取消订阅主订阅时,它也会取消订阅所有添加的订阅.

  • 提示 2 - 是一个专业提示,适用于任何想要通过仅调用一个函数来取消多个订阅的人。使用 .add() 方法添加,然后在 ngDestroy 中使用 .unsubscribe() 。 (3认同)
  • 对于那些在 2022 年及以后接触到这一点的人来说,更专业的提示是使用 Net Basal 的 https://github.com/ngneat/until-destroy 包。您可以稍后感谢我 (3认同)
  • 另请注意,如果您有一个返回原始 http overvable 的服务,然后在订阅之前通过管道传输它,那么您只需要取消订阅最终的 observable,而不是底层的 http observable。事实上,你甚至不会直接订阅 http,所以你不能。 (2认同)

Thi*_*ier 22

调用该unsubscribe方法更确切地说是取消正在进行的HTTP请求,因为此方法调用abort底层XHR对象上的那个并删除有关加载和错误事件的侦听器:

// From the XHRConnection class
return () => {
  _xhr.removeEventListener('load', onLoad);
  _xhr.removeEventListener('error', onError);
  _xhr.abort();
};
Run Code Online (Sandbox Code Playgroud)

那说,unsubscribe删除听众......所以这可能是一个好主意,但我不认为这是一个单一的请求;-)

希望它对你有帮助,蒂埃里


Lux*_*lem 17

经过一段时间的测试,阅读文档和 HttpClient 的源代码。

HttpClient: https://github.com/angular/angular/blob/master/packages/common/http/src/client.ts

HttpXhrBackend : https://github.com/angular/angular/blob/master/packages/common/http/src/xhr.ts

HttpClientModule: https://indepth.dev/exploring-the-httpclientmodule-in-angular/

角大学:https : //blog.angular-university.io/angular-http/

这种特殊类型的 Observables 是单值流:如果 HTTP 请求成功,这些 Observables 将只发出一个值然后完成

以及“我是否需要”取消订阅的整个问题的答案?

这取决于。 Http 调用 Memoryleaks 不是问题。问题是回调函数中的逻辑。

例如:路由或登录。

如果您的呼叫是登录呼叫,您不必“取消订阅”,但您需要确保用户离开页面,在用户不在的情况下正确处理响应。


this.authorisationService
      .authorize(data.username, data.password)
      .subscribe((res: HttpResponse<object>) => {
          this.handleLoginResponse(res);
        },
        (error: HttpErrorResponse) => {
          this.messageService.error('Authentication failed');
        },
        () => {
          this.messageService.info('Login has completed');
        })
Run Code Online (Sandbox Code Playgroud)

从烦人到危险

现在想象一下,网络比平时慢,通话需要更长的 5 秒,用户离开登录视图并转到“支持视图”。

该组件可能不是活动的,但订阅。如果有响应,用户将被突然重新路由(取决于您的 handleResponse() 实现)。

这是不是好。

也想象一下用户离开电脑,认为他还没有登录。但是您的逻辑让用户登录,现在您遇到了安全问题。

如果不取消订阅,您可以做什么?

使您调用依赖于视图的当前状态:

  public isActive = false;
  public ngOnInit(): void {
    this.isActive = true;
  }

  public ngOnDestroy(): void {
    this.isActive = false;
  }

Run Code Online (Sandbox Code Playgroud)

用户.pipe(takeWhile(value => this.isActive))确保仅在视图处于活动状态时才处理响应。


this.authorisationService
      .authorize(data.username, data.password).pipe(takeWhile(value => this.isActive))
      .subscribe((res: HttpResponse<object>) => {
          this.handleLoginResponse(res);
        },
        (error: HttpErrorResponse) => {
          this.messageService.error('Authentication failed');
        },
        () => {
          this.messageService.info('Login has completed');
        })
Run Code Online (Sandbox Code Playgroud)

但是您如何确定订阅不会导致内存泄漏?

您可以记录是否应用了“teardownLogic”。

当订阅为空或取消订阅时,会调用订阅的teardownLogic。


this.authorisationService
      .authorize(data.username, data.password).pipe(takeWhile(value => this.isActive))
      .subscribe((res: HttpResponse<object>) => {
          this.handleLoginResponse(res);
        },
        (error: HttpErrorResponse) => {
          this.messageService.error('Authentication failed');
        },
        () => {
          this.messageService.info('Login has completed');
    }).add(() => {
        // this is the teardown function
        // will be called in the end
      this.messageService.info('Teardown');
    });

Run Code Online (Sandbox Code Playgroud)

您不必取消订阅。您应该知道您的逻辑是否存在问题,这可能会导致您的订阅出现问题。并照顾他们。在大多数情况下,这不会成为问题,但特别是在诸如自动化之类的关键任务中,您应该注意意外行为,无论是“取消订阅”还是其他逻辑,如管道或条件回调函数。

为什么不总是退订?

想象一下,您发出放置或发布请求。服务器以任何一种方式接收消息,只是响应需要一段时间。取消订阅,不会撤消帖子或放置。但是当您取消订阅时,您将没有机会处理响应或通知用户,例如通过 Dialog 或 Toast/Message 等。

这让用户相信,放置/发布请求没有完成。

所以这取决于。如何处理此类问题取决于您的设计决策。


小智 11

使用新的HttpClient模块,仍然保持相同的行为 包/通用/ HTTP/src目录/ jsonp.ts


小智 8

您不应该取消订阅自动完成的可观察量(例如,Http,调用).但是有必要取消订阅无限的可观察量Observable.timer().


MTZ*_*MTZ 7

你绝对应该阅读这篇文章。它向您展示了为什么即使从 http也应该始终取消订阅

如果在创建请求之后但在从后端收到答复之前您认为该组件没有必要并销毁它,则您的订阅将维护对该组件的引用,从而有可能导致内存泄漏。

更新

上面的肯定似乎是对的,但是无论如何,当答案回来时,http订阅无论如何都会被破坏

  • 是的,您实际上会在浏览器的网络选项卡中看到红色的“取消”,因此您可以确定它可以正常工作。 (2认同)

un3*_*33k 5

如果要在所有网络速度上都具有确定性,必须取消订阅

想象一下,组件A在选项卡中呈现-您单击按钮以发送“ GET”请求。恢复响应需要200毫秒。因此,您可以随时安全地关闭选项卡,因为它知道,计算机将比您更快,并且在关闭选项卡和销毁组件A之前,http响应已处理且已完成。

在非常慢的网络上怎么样?您单击一个按钮,“ GET”请求需要10秒钟才能收到响应,但是等待5秒钟等待您决定关闭该标签。这将销毁要在以后垃圾回收的组件A。 等一下!,我们并没有取消订阅-现在5秒钟后,响应又回来了,销毁组件中的逻辑将被执行。现在考虑执行该命令out-of-context,它可能导致很多事情,包括非常低的性能。

因此,最佳实践是在takeUntil()销毁组件时使用和取消订阅http调用。

import { Component, OnInit, OnDestroy } from '@angular/core';
import { HttpClient } from '@angular/common/http';

import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';

interface User {
  id: string;
  name: string;
  age: number;
}

@Component({
  selector: 'app-foobar',
  templateUrl: './foobar.component.html',
  styleUrls: ['./foobar.component.scss'],
})
export class FoobarComponent implements OnInit, OnDestroy {
  private user: User = null;
  private destroy$ = new Subject();

  constructor(private http: HttpClient) {}

  ngOnInit() {
    this.http
      .get<User>('api/user/id')
      .pipe(takeUntil(this.destroy$))
      .subscribe(user => {
        this.user = user;
      });
  }

  ngOnDestroy(): void {
    this.destroy$.next();  // trigger the unsubscribe
    this.destroy$.complete(); // finalize & clean up the subject stream
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 无论如何,你应该取消订阅,假设有一个拦截器来处理错误,但你已经离开了触发错误的页面..你将在另一个页面中看到一条错误消息....还要考虑一个健康检查页面调用所有微服务...等等 (3认同)
  • 无需取消订阅 HttpClient 可观察量,因为它们是有限可观察量,即它们在发出值后完成。 (2认同)