是否需要退订Http方法创建的可观察对象?


211

您是否需要取消订阅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());
            ...


Answers:


253

所以答案是否定的,你不是。Ng2会自行清理。

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

在此处输入图片说明

注意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);
    };
}

如预期的那样,您可以看到在获得结果并完成可观察到的运算符后,它总是自动取消订阅。这是在超时(#3)上发生的,因此我们可以检查可观察对象全部完成和完成的状态。

结果

在此处输入图片说明

因此,不存在Ng2自动退订的泄漏!

尼斯提及:这Observable被分类为finite,上违背了infinite Observable其是数据的无限流可被发射的像DOM click侦听例子。

谢谢@rubyboy。


17
如果用户在响应进入之前就离开页面,或者响应从未在用户离开视图之前就出现,会发生什么情况?那不会造成泄漏吗?
锡伯斯

1
我也想知道以上内容。
1984年

4
@ 1984答案总是取消订阅。正是由于此评论中提出的原因,这个答案是完全错误的。导航离开的用户是一个内存链接。另外,如果该订阅中的代码在用户离开后运行,则可能导致错误/产生意外的副作用。我倾向于在所有可观察对象上使用takeWhile(()=> this.componentActive),并在ngOnDestroy中将this.componentActive = false设置为清除组件中的所有可观察对象。
莱尼

4
此处显示的示例涵盖了一个深角度的私有api。与任何其他私有api一样,它可能会随着升级而更改,恕不另行通知。经验法则是:如果未正式记录,请勿做任何假设。例如,AsynPipe具有清晰的文档,它会自动为您订阅/取消订阅。HttpClient文档未提及任何内容。
YoussefTaghlabi

1
@YoussefTaghlabi,仅供参考,官方角度文档(angular.io/guide/http)确实提到:“ AsyncPipe自动为您订阅(和取消订阅)。” 因此,它确实已正式记录在案。
哈迪

96

你们在说什么!!!

好的,所以有两个原因取消订阅任何可观察的消息。似乎没人在谈论非常重要的第二个原因!

1)清理资源。正如其他人所说,对于HTTP可观察对象来说,这是可以忽略的问题。它会自行清理。

2)阻止subscribe处理程序运行。

(对于HTTP来说,这实际上还会取消浏览器中的请求-这样就不会浪费时间阅读响应。但这实际上是我在下面要讲的内容之一。)

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

如果您的subscribe()处理程序函数具有某种副作用,而无论调用它是关闭还是处置,这都是不希望的,那么您必须取消订阅(或添加条件逻辑)以防止其执行。

考虑以下几种情况:

1)登录表单。输入用户名和密码,然后单击“登录”。如果服务器运行缓慢并且您决定按Escape键关闭对话框怎么办?您可能会假设您尚未登录,但是如果在转义后返回了http请求,那么您仍将执行那里的任何逻辑。这可能会导致重定向到帐户页面,设置了不必要的登录cookie或令牌变量。这可能不是您的用户所期望的。

2)“发送电子邮件”表格。

如果subscribe“ sendEmail” 的处理程序执行触发“您的电子邮件已发送”动画之类的操作,将您转到其他页面或尝试访问已处理的任何内容,则可能会出现异常或不良行为。

同样要注意不要假设unsubscribe()手段“取消”。运行HTTP消息后,unsubscribe()如果已经到达您的服务器,则不会取消HTTP请求。它只会取消返回给您的响应。电子邮件可能会被发送。

如果创建订阅以直接在UI组件内发送电子邮件,则可能希望取消订阅处理,但是如果电子邮件是由非UI集中式服务发送的,则可能不需要。

3)被破坏/关闭的Angular组件。除非您取消订阅,否则当时仍在运行的所有HTTP可观察对象都将完成并运行其逻辑onDestroy()。结果是否微不足道取决于您在订阅处理程序中执行的操作。如果您尝试更新不存在的内容,则可能会收到错误消息。

有时,如果组件被处置,您可能会希望执行某些操作,而有些则不需要。例如,对于发送的电子邮件,您可能会发出“ s”的声音。即使关闭了组件,您可能也希望播放该动画,但是如果尝试在该组件上运行动画,则动画将失败。在这种情况下,解决方案内部将包含一些额外的条件逻辑,而您将不想取消订阅可观察的http。

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

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

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


2
还要注意,如果您有一个返回原始HTTP可覆盖对象的服务,然后在订阅之前通过管道将其订阅,那么您只需要取消订阅最终的可观察对象,而不是基础的HTTP可观察对象。实际上,您甚至没有直接订阅http的订阅,因此您没有。
Simon_Weaver

即使取消订阅会取消浏览器请求,服务器仍会对该请求执行操作。有没有办法使服务器也中止操作?我正在快速连续发送HTTP请求,其中的大多数请求都因取消订阅而被取消。但是,服务器仍然对客户端取消的请求进行操作,从而导致合法请求等待。
巴拉

@bala为此,您必须提出自己的机制-与RxJS没有任何关系。例如,您可能只是将请求放入表中,并在后台运行5秒钟,然后,如果需要取消某些操作,则只需删除或在较旧的行上设置标志以使其停止执行。将完全取决于您的应用程序是什么。但是,由于您提到服务器正在阻止,因此可以将其配置为一次只允许一个请求-但这又取决于您使用的内容。
Simon_Weaver

🔥🔥🔥提示2-是PRO-TIP ,,想要通过仅调用一个功能来取消订阅的任何人。使用.add()方法进行添加,然后使用ngDestroy中的.unsubscribe()进行添加。
abhay tripathi

23

调用该unsubscribe方法不是取消正在进行的HTTP请求,因为此方法abort在基础XHR对象上调用一个,并删除load和error事件上的侦听器:

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

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

希望对您有帮助,蒂埃里


:| 太荒谬了:| 我一直在寻找一种停止的方式...但是我很纳闷,在某些情况下,如果是我编码的话,我会这样做:y = x.subscribe((x)=>data = x); 然后用户输入更改,然后x.subscribe((x)=>cachlist[n] = x); y.unsubscribe()您使用了我们的服务器资源,我赢了不会把你扔掉……..在其他情况下:只需打电话y.stop()把所有东西扔掉
deadManN

16

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

想象一下,组件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
  }
}

它是怎么运用BehaviorSubject(),而不是,只是打电话complete()ngOnDestroy()
萨克斯姆

您仍然需要退订...为什么会BehaviourSubject有所不同?
Ben Taliadoros

无需取消订阅HttpClient可观察变量,因为它们是有限的可观察变量,即它们在发出值后完成。
Yatharth Varshney

无论如何,您都应该退订,假设有一个拦截器来敬酒错误,但是您已经离开了触发错误的页面。您将在另一页面中看到错误消息...。还要考虑一下运行状况检查页面,调用所有微服务……等等
华盛顿瓜迪斯

12

同样与新的HttpClient模块保持相同的行为 包/普通/http/src/jsonp.ts


上面的代码似乎是从一个单元测试,这意味着与HttpClient的,你对自己的(确实需要调用.complete()github.com/angular/angular/blob/...
CleverPatrick

是的,您是对的,但是如果您检查(github.com/angular/angular/blob/…),您将看到相同的内容。关于主要问题,是否有必要明确退订?大多数情况下不会,因为它将由Angular自己处理。在这种情况下,可能会发生较长的响应,并且您已移至另一条路线,或者可能损坏了组件,在这种情况下,您可能会尝试访问不再存在的内容,从而引发异常。
里卡多·马丁内斯


6

经过一段时间的测试,阅读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

HttpClientModulehttps//indepth.dev/exploring-the-httpclientmodule-in-angular/

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

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

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

这取决于。 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');
        })

从烦人到危险

现在,想象一下,网络速度比平时慢,通话时间要长5秒钟,并且用户离开登录视图并进入“支持视图”。

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

这是不是好。

还可以想象用户离开PC,以为他尚未登录。但是您的逻辑使用户登录,现在您遇到了安全问题。

如果不取消订阅该怎么办?

让您根据视图的当前状态进行调用:

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

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

用户.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');
        })

但是,如何确定订阅不会引起内存泄漏呢?

您可以记录是否应用了“ 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');
    });

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

为什么不总是退订?

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

Wich使用户相信未完成放置/发布请求。

因此,这取决于。这是您的设计决策,如何处理此类问题。


4

你一定要读条。它向您展示了为什么即使仍要从http退订

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

更新资料

上面的确认似乎是正确的,但是无论如何,当答案返回时,http订阅仍然被销毁。


1
是的,您实际上会在浏览器的“网络”标签中看到一个红色的“取消”,因此可以确定它可以正常工作。
Simon_Weaver

-2

可观察的RxJS基本上是相关联的,因此您订阅它就可以相应地工作。当我们创建可观察对象并完成移动时,可观察对象会自动关闭并取消订阅。

它们的工作方式与观察者相同,但顺序却完全不同。更好的做法是在组件销毁时退订它们。this。$ manageSubscription.unsubscibe()

如果我们创建了如下所述的observable语法,例如

** return new Observable((observer)=> {** //它在冷态下可观察到** observer.complete()**}) **

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.