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

你需要取消订阅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());
...
118770 次浏览

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

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

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

希望它能帮助你, 蒂埃里< / p >

所以答案是不,你不需要。Ng2将自行清理。

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

enter image description here

注意它在得到结果后是如何运行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),所以我们可以在它全部完成和完成时检查可观察对象的状态。

结果是

enter image description here

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

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

谢谢@rubyboy的帮助。

对于新的HttpClient模块,仍然保持相同的行为 packages/common/http/src/json .ts < / p >

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

RxJS的可观察对象基本上是相关联的,并根据订阅它来工作。当我们创建可观察对象并完成移动时,可观察对象自动关闭并取消订阅。

他们以同样的方式工作,但顺序完全不同。 最好的做法是在组件被销毁时取消订阅。我们随后可以通过如。manageSubscription.unsubscibe美元。()

来实现

如果我们像下面提到的语法一样创建了可观察对象

< p > * * return new Observable((observer) => { ** //在冷状态下是可观察的 * * observer.complete () * *}) < / >强 * * < / p >

你们这些人在说什么!!

所以我们有两个理由取消订阅任何可观察对象。似乎没有人谈论非常重要的第二个原因!

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

注意,使用HTTP可观察对象,当你取消订阅时,它会在浏览器中为你取消底层请求(你会在网络面板中看到“取消”的红色部分)。这意味着不会浪费时间下载或解析响应。但这实际上是我下面主要观点的题外话。

2的相关性将取决于你的订阅处理程序做什么:

如果你的subscribe()处理函数有任何不希望的副作用 如果调用它的对象被关闭或被释放,那么你必须取消订阅(或添加条件逻辑)来阻止它被执行

考虑以下几个案例:

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

  2. 发送邮件表单。

如果“sendEmail”的subscribe处理程序做了一些事情,比如触发“您的电子邮件已发送”动画,将您转移到另一个页面或试图访问任何已处理的内容,您可能会得到异常或不想要的行为。

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

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

  1. 一个被销毁/关闭的Angular组件。此时仍在运行的任何http可观察对象将完成并运行它们的逻辑,除非你在onDestroy()中取消订阅。结果是否微不足道取决于您在订阅处理程序中所做的工作。如果你试图更新一些不存在的东西,你可能会得到一个错误。

有时,如果组件被释放,您可能需要一些操作,而有些则不需要。例如,你发出的电子邮件可能会发出“嗖嗖”的声音。你可能想要在组件关闭时播放这个,但是如果你试图在组件上运行动画,它会失败。在这种情况下,订阅内部的一些额外的条件逻辑将是解决方案-你不会想要取消订阅http可观察对象。

所以在实际问题的答案中,不,你不需要这样做来避免内存泄漏。但您需要(经常)这样做,以避免运行代码引发不必要的副作用,这些代码可能会抛出异常或破坏应用程序状态。

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

技巧2:如果处理多个订阅,你可以创建一个特别的new Subscription()对象和add(...)对它的任何其他订阅——所以当你从主订阅中取消订阅时,它也会取消所有添加的订阅。

你绝对应该阅读文章。它告诉你为什么你应该总是取消订阅,甚至从HTTP

如果在创建请求之后,但在接收到来自 后端,您认为组件是不必要的,并销毁它,您的 订阅将这样维护对组件的引用

.创建一个导致内存泄漏的机会

更新

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

如果你想在所有网速上都有确定的行为,那么取消订阅就是一个必须

想象一下组件A被呈现在一个选项卡-你点击一个按钮发送一个'GET'请求。响应返回需要200毫秒。所以,你是安全的关闭标签在任何时候知道,机器将比你&在标签被关闭和组件A被销毁之前,http响应被处理并完成。

在网速很慢的情况下呢?你点击一个按钮,“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
}
}

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

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

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

而整个问题的答案是“我需要退订吗?”

< >强视情况而定。 Http调用内存泄漏不是一个问题。 问题是回调函数中的逻辑。< / p >

例如:路由、登录。

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


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秒,用户离开登录视图,转到“支持视图”。

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

这是好。

再想象一下,用户离开了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');
});


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

为什么不总是退订呢?

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

这使得用户相信,put/post请求没有完成。

这要看情况。如何处理这些问题是你的设计决定。

有助于理解这一点的一个很好的部分是,除非调用subscribe函数,否则不会发出HTTP请求。虽然本页上的答案似乎建议了两种不同的做法,但它真的没有多大意义 如angular 文档所示(尽管它将在后面的“发出DELETE请求”一节中提到):

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

事实上,在文档中更难找到通过调用unsubscribe函数显式取消订阅这些可观察对象的例子。

我建立了一个庞大的企业应用程序,相信httpclient会自动退订,我没有遇到任何内存泄漏。