【问题标题】:Is it necessary to unsubscribe from observables created by Http methods?是否有必要取消订阅由 Http 方法创建的可观察对象?
【发布时间】:2016-05-04 17:35:46
【问题描述】:

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

【问题讨论】:

标签: angular memory-leaks rxjs angular2-http


【解决方案1】:

所以答案是否定的,你没有。 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) 上,因此我们可以在所有完成和完成时检查 observable 的状态。

结果

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

值得一提的是:这个Observable 被归类为finite,而infinite Observable 则相反,它可以像DOM click 监听器一样发出无限的数据流。

感谢@rubyboy 对此的帮助。

【讨论】:

  • 如果用户在响应出现之前离开页面,或者在用户离开视图之前响应从未出现,会发生什么情况?这不会导致泄漏吗?
  • @1984 答案总是取消订阅。由于此评论中提出的原因,该答案完全错误。导航离开的用户是一个记忆链接。另外,如果该订阅中的代码在用户导航离开后运行,则可能会导致错误/产生意外的副作用。我倾向于在所有可观察对象上使用 takeWhile(()=>this.componentActive) 并在 ngOnDestroy 中设置 this.componentActive=false 以清理组件中的所有可观察对象。
  • 此处显示的示例涵盖了一个深角私有 api。使用任何其他私有 api,它可以随升级而更改,恕不另行通知。经验法则是:如果没有正式记录,不要做任何假设。例如,AsynPipe 有明确的文档,它会自动为您订阅/取消订阅。 HttpClient 文档没有提及任何相关内容。
  • @YoussefTaghlabi,仅供参考,官方 Angular 文档 (angular.io/guide/http) 确实提到:“AsyncPipe 会自动为您订阅(和取消订阅)。”所以,它确实有官方记录。
  • @Hudgi,您所说的是当您使用异步管道时......但这是当您直接在 html 模板中使用 observable 时。例如:obsrvbl$ |异步
【解决方案2】:

你们在说什么!!!

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

  1. 清理资源。正如其他人所说,这对于 HTTP observables 来说是一个可以忽略不计的问题。它会自行清理。
  1. 阻止subscribe 处理程序运行。

请注意,对于 HTTP observable,当您取消订阅时,它将在您的浏览器中为您取消底层请求(您会在网络面板中看到红色的“已取消”)。这意味着时间不会浪费在下载或解析响应上。但这实际上是我在下面的主要观点的一个旁白。

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

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

考虑几个案例:

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

  2. “发送电子邮件”表单。

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

还要注意不要假设unsubscribe() 表示“取消”。一旦 HTTP 消息在运行中,unsubscribe() 将不会取消 HTTP 请求,如果它已经到达您的服务器。它只会取消返回给您的响应。并且电子邮件可能会被发送。

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

  1. 一个被销毁/关闭的 Angular 组件。除非您取消订阅 onDestroy(),否则当时仍在运行的任何 http observables 都将完成并运行它们的逻辑。结果是否微不足道取决于您在订阅处理程序中所做的事情。如果您尝试更新不再存在的内容,您可能会收到错误消息。

有时,如果组件被释放,您可能会执行一些您想要的操作,而有些则不会。例如,您发送的电子邮件可能会发出“嗖嗖”声。即使组件已关闭,您也可能希望它播放,但如果您尝试在组件上运行动画,它将失败。在这种情况下,订阅中的一些额外条件逻辑将是解决方案 - 您不想取消订阅 http observable。

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

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

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

【讨论】:

  • 另外请注意,如果您有一个返回原始 http overvable 的服务,然后您在订阅之前对其进行管道传输,那么您只需要取消订阅最终的 observable,而不是底层的 http observable。事实上,你甚至不会直接订阅 http,所以你不能。
  • 即使取消订阅会取消浏览器请求,但服务器仍然会处理该请求。有没有办法让服务器也中止操作?我正在快速连续发送 http 请求,其中大部分被取消订阅取消。但是,服务器仍然对客户端取消的请求进行操作,导致合法请求等待。
  • @bala 你必须为此想出自己的机制——这与 RxJS 没有任何关系。例如,您可以将请求放入表中并在 5 秒后在后台运行它们,然后如果需要取消某些内容,您只需删除旧行或在旧行上设置一个标志以阻止它们执行。完全取决于您的应用程序是什么。但是,由于您提到服务器正在阻塞,因此可能会将其配置为一次只允许一个请求 - 但这又取决于您使用的是什么。
  • ???提示 2 - 是一个专业提示,??? 适合任何想要通过调用一个函数来取消订阅多个订阅的人。使用 .add() 方法添加,而不是 ngDestroy 中的 .unsubscribe()。
【解决方案3】:

取消订阅必须,如果您希望在所有网络速度上都有确定性行为。

想象组件 A 在选项卡中呈现 - 您单击一个按钮以发送“GET”请求。响应返回需要 200 毫秒。因此,您可以随时安全地关闭选项卡,因为机器会比您快,并且 http 响应已被处理,并且在选项卡关闭和组件 A 被销毁之前完成。

在非常慢的网络上怎么样?您单击一个按钮,“GET”请求需要 10 秒才能收到响应,但等待 5 秒后您决定关闭该选项卡。这将破坏组件 A 以便稍后进行垃圾收集。 等一下!,我们没有取消订阅 -- 现在 5 秒后,响应回来了,被销毁组件中的逻辑将被执行。该执行现在被视为out-of-context,并可能导致许多事情,包括非常低的性能和数据/状态损坏。

因此,最佳做法是使用takeUntil(),并在组件被销毁时取消订阅 http 调用订阅。

注意:

  • RxJS 不是 Angular 特定的
  • 模板中使用的 Angular async 管道在销毁时自动取消订阅
  • 多次退订没有负面影响,除了额外的no-op 调用
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() 并在ngOnDestroy() 中只调用complete() 吗?
  • 您仍然需要取消订阅...为什么BehaviourSubject 会有所不同?
  • 不需要取消订阅 HttpClient 可观察对象,因为它们是有限的可观察对象,即它们在发出值后完成。
  • 无论如何你都应该取消订阅,假设有一个拦截器来吐司错误,但是你已经离开了触发错误的页面..你会在另一个页面中看到一条错误消息....还有考虑一个调用所有微服务的健康检查页面......等等
  • 所提供示例中的“取消订阅”行为在哪里?
【解决方案4】:

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

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

也就是说,unsubscribe 删除了侦听器...所以这可能是个好主意,但我认为单个请求没有必要 ;-)

希望对你有帮助 蒂埃里

【讨论】:

  • :|这太荒谬了:|我正在寻找一种停止的方法......但我想知道,如果是我在编码,在某些情况下:我会这样做:y = x.subscribe((x)=&gt;data = x); 然后用户更改输入和x.subscribe((x)=&gt;cachlist[n] = x); y.unsubscribe() 嘿,你用过我们的服务器资源,我不会把你扔掉的......在其他情况下:只需调用y.stop()把所有东西都扔掉
【解决方案5】:

经过一段时间的测试、阅读文档和 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/

这种特殊类型的 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() 实现)。

不好

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

不退订还能做什么?

让你调用依赖于视图的当前状态:

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

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

用户.pipe(takeWhile(value =&gt; 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');
    });

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

为什么不总是退订?

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

这会让用户相信 put/post 请求没有完成。

所以这取决于。如何处理这些问题是您的设计决定。

【讨论】:

    【解决方案6】:

    同样使用新的 HttpClient 模块,保持相同的行为

    【讨论】:

    • 以上代码似乎来自单元测试,暗示使用 HttpClient,您需要自己调用 .complete() (github.com/angular/angular/blob/…)
    • 是的,你是对的,但如果你检查 (github.com/angular/angular/blob/…) 你会看到同样的结果。关于主要问题,是否有必要明确退订?大多数情况下没有,因为将由 Angular 自己处理。可能是这样的情况:可能会发生长响应,并且您已移动到另一条路线或可能破坏了组件,在这种情况下,您可能会尝试访问不再存在的东西,从而引发异常。跨度>
    【解决方案7】:

    您绝对应该阅读this 文章。它向您展示了为什么您应该即使从 http 也总是退订

    如果在创建请求之后但在收到来自 后端您认为该组件不必要并销毁它,您的 订阅将保持对组件的引用,因此 造成内存泄漏的机会。

    更新

    上面的断言似乎是真的,但是无论如何,当答案回来时,http订阅无论如何都会被销毁

    【讨论】:

    • 是的,您实际上会在浏览器的网络选项卡中看到一个红色的“取消”,因此您可以确定它可以正常工作。
    • 链接已损坏。
    • @robert 是的,现在好像坏了。
    【解决方案8】:

    您不应取消订阅自动完成的 observables(例如 Http、调用)。但是有必要取消订阅像Observable.timer() 这样的无限可观察对象。

    【讨论】:

      【解决方案9】:

      有助于理解这一点的一个很好的部分是,除非调用 subscribe 函数,否则不会发出 HTTP 请求。尽管此页面上的答案似乎暗示了两种不同的做法,但实际上并没有多大意义 差异,因为所需的行为将由异步管道控制,如角度 docs 所示(尽管稍后在“发出删除请求”部分中提到):

      AsyncPipe 会自动为您订阅(和取消订阅)。

      事实上,更难在文档中找到通过调用 unsubscribe 函数显式取消订阅此类 observable 的示例。

      【讨论】:

        【解决方案10】:

        RxJS observable 基本上是相关联的,并且在您订阅它时会相应地工作。当我们创建 observable 并完成它的移动时,observable 会自动关闭和取消订阅。

        它们以与观察者相同的方式工作,但顺序完全不同。 当组件被破坏时取消订阅它们的更好做法。我们随后可以通过 来做到这一点。 this.$manageSubscription.unsubscibe()

        如果我们创建了像下面提到的语法这样的 observable,例如

        ** return new Observable((observer) => { ** // 在冷态下可以观察到 ** 观察者。完成() **}) **

        【讨论】:

          猜你喜欢
          • 2021-06-14
          • 2019-03-28
          • 2022-01-23
          • 2017-05-11
          • 2019-01-31
          • 2017-05-15
          • 1970-01-01
          • 2019-11-15
          相关资源
          最近更新 更多