如何在 Angular 4+ 中取消/取消订阅所有挂起的 HTTP 请求

Posted

技术标签:

【中文标题】如何在 Angular 4+ 中取消/取消订阅所有挂起的 HTTP 请求【英文标题】:How to cancel/unsubscribe all pending HTTP requests in Angular 4+ 【发布时间】:2018-02-14 13:43:59 【问题描述】:

如何在 Angular 4+ 中取消/中止所有挂起的 HTTP 请求。

有一个unsubscribe 方法可以取消 HTTP 请求,但是如何一次性取消所有挂起的请求。

尤其是在路线变化时。

我做了一件事

ngOnDestroy() 
  this.subscription.unsubscribe();

但是如何在全球范围内实现这一目标

有什么想法吗?

【问题讨论】:

利用拦截器并使用超时 是的。拦截器。我忘了我会调查一下 拦截器没有帮助@SibiRaj 不。我试过了。但似乎没有任何帮助。我可以有时间但无法将它们与路线一起使用。你有什么收获吗? 看看这篇文章:***.com/a/41177163/6184203 【参考方案1】:

签出 RxJS 中的 takeUntil() 运算符以在全球范围内取消您的订阅:

- RxJS 6+(使用pipe 语法)

import  takeUntil  from 'rxjs/operators';

export class YourComponent 
   protected ngUnsubscribe: Subject<void> = new Subject<void>();

   [...]

   public httpGet(): void 
      this.http.get()
          .pipe( takeUntil(this.ngUnsubscribe) )
          .subscribe( (data) =>  ... );
   

   public ngOnDestroy(): void 
       // This aborts all HTTP requests.
       this.ngUnsubscribe.next();
       // This completes the subject properlly.
       this.ngUnsubscribe.complete();
   

- RxJS

import 'rxjs/add/operator/takeUntil'

export class YourComponent 
   protected ngUnsubscribe: Subject<void> = new Subject<void>();

   [...]

   public httpGet(): void 
      this.http.get()
         .takeUntil(this.ngUnsubscribe)
         .subscribe( (data) =>  ... )
   

   public ngOnDestroy(): void 
       this.ngUnsubscribe.next();
       this.ngUnsubscribe.complete();
   

您基本上可以在每次想要完成一堆流时使用next() 在取消订阅Subject 上发出一个事件。在组件被销毁时取消订阅活动的 Observable 也是一种很好的做法,以避免内存泄漏。

值得一读:

Avoiding take until leaks

A great answer from seangwright

【讨论】:

查看 seangwright 关于该主题的非常完整的答案;)***.com/a/41177163/7152435 希望对您有所帮助! 别忘了从 RxJs 导入 takeUntil,import 'rxjs/add/operator/takeUntil';【参考方案2】:

您可以创建一个拦截器以将takeUntil 运算符应用于每个请求。然后在路由更改时,您将发出将取消所有待处理请求的事件。

@Injectable()
export class HttpCancelInterceptor implements HttpInterceptor 
  constructor(private httpCancelService: HttpCancelService)  

  intercept<T>(req: HttpRequest<T>, next: HttpHandler): Observable<HttpEvent<T>> 
    return next.handle(req).pipe(takeUntil(this.httpCancelService.onCancelPendingRequests()))
  

助手服务。

@Injectable()
export class HttpCancelService 
  private cancelPendingRequests$ = new Subject<void>()

  constructor()  

  /** Cancels all pending Http requests. */
  public cancelPendingRequests() 
    this.cancelPendingRequests$.next()
  

  public onCancelPendingRequests() 
    return this.cancelPendingRequests$.asObservable()
  


在您的应用中的某处挂钩路由更改(例如,appComponent 中的 onInit)。

this.router.events.subscribe(event => 
  if (event instanceof ActivationEnd) 
    this.httpCancelService.cancelPendingRequests()
  
)

最后但同样重要的是,将拦截器注册到您的 app.module.ts:

  import  HttpCancelInterceptor  from 'path/to/http-cancel.interceptor';
  import  HTTP_INTERCEPTORS  from '@angular/common/http';

  @NgModule(
    [...]
    providers: [
      
        multi: true,
        provide: HTTP_INTERCEPTORS,
        useClass: HttpCancelInterceptor
      
    ],
    [...]
  )
  export class AppModule  

【讨论】:

我认为这是最好的方法 这看起来很棒,但是忘记完成主题以关闭它 @hbthanki 是的,您需要手动取消其他请求。由于取消订阅所有内容可能很烦人,我通常有一个实现 onDestroy 的类,并且我的组件扩展了这个类(我称之为 Destroyable)。它有一个公共主题,在销毁时发出并完成。然后,我的组件在每个 observable 上都有一个 takeUntil(this.destroyed$)。因此,这种方法会在您销毁组件时取消所有待处理的可观察对象。 @Logus 我没有故意关闭它,因为该服务在应用程序存在的整个时间内都存在,并且关闭流无论如何都不会释放任何资源。如果主题完成了,那么您将需要一遍又一遍地创建新主题。它什么时候会创建?谁来承担这个责任?它只会使代码更复杂,我担心它不会增加任何价值。如果我错了,请随时纠正我。 感谢您的有用回答。我只是想添加 'next.handle(req).takeUntil()' 不起作用。(使用 angular 7 和 rxjs 版本 6.3.3 对我不起作用)。我用 next.handle(req).pipe(takeUntil()) 代替。【参考方案3】:

如果您不想手动取消订阅所有订阅,则可以这样做:

export function AutoUnsubscribe(constructor) 

  const original = constructor.prototype.ngOnDestroy;

  constructor.prototype.ngOnDestroy = function() 
    for (const prop in this) 
      if (prop) 
        const property = this[prop];
        if (property && (typeof property.unsubscribe === 'function')) 
          property.unsubscribe();
        
      
    

    if (original && typeof original === 'function') 
      original.apply(this, arguments)
    ;
  ;


然后你可以在你的组件中使用它作为装饰器

@AutoUnsubscribe
export class YourComponent  

但您仍然需要将订阅存储为组件属性。 并且当您导航出组件时,会出现自动取消订阅功能。

【讨论】:

我喜欢这个主意。我是否可以建议您通过处理具有订阅数组(并不罕见)的组件来使其更加健壮?例如。 (Array.isArray(property) ? property : [property]).filter(property =&gt; isFunction(property.unsubscribe)).forEach(property =&gt; property.unsubscribe()));. 这是个好主意,但需要优化一下,因为如果你有大数据的数组,那么filter会搜索每个元素,可能会有点慢。可能我们只能检查数组的第一个元素,如果它的 subsctiption 那么我们可以假设所有数组都是订阅元素。 你可以这样做,但我怀疑这会对性能产生重大影响。【参考方案4】:

我不相信需要所请求的功能,但您可以做到这一点,通过包装框架的 http 服务并委托给它,随时随地取消所有未完成的请求。

但是,当我们着手实施这项服务时,问题很快就会显现出来。一方面,我们希望避免更改现有代码,包括第三方代码,这些代码利用了现有的 Angular http 客户端。另一方面,我们希望避免实现继承。

为了两全其美,我们可以使用我们的包装器实现 Angular Http 服务。现有代码将继续工作而无需更改(前提是所述代码不会做任何愚蠢的事情,例如使用 http instanceof Http)。

import Http, Request, RequestOptions, RequestOptionsArgs, Response from '@angular/http';
import Observable from 'rxjs/Observable';
import Subscription from 'rxjs/Subscription';



export default interface CancellationAwareHttpClient extends Http  

export default class CancellationAwareHttpClient 
  constructor(private wrapped: Http) 
    const delegatedMethods: Array<keyof Http> = [
      'get', 'post', 'put', 'delete',
      'patch', 'head', 'options'
    ];
    for (const key of delegatedMethods) 
      this[key] = wrapped[key].bind(wrapped);
    
  

  cancelOutstandingRequests() 
    this.subscriptions.forEach(subscription => 
      subscription.unsubscribe();
    );
    this.subscriptions = [];
  

  request(url: string | Request, options?: RequestOptionsArgs) 
    const subscription = this.wrapped.request(url, options);
    this.subscriptions.push(subscription);
    return subscription;
  

  subscriptions: Subscription[] = [];

请注意,CancellationAwareHttpClientinterfaceclass 声明已合并。这样,我们的类实现 Http 凭借interface 声明的extends 子句。

现在我们将提供我们的服务

import NgModule from '@angular/core';
import ConnectionBackend, RequestOptions from '@angular/http';

import CancellationAwareHttpClient from 'app/services/cancellation-aware-http-client';

let cancellationAwareClient: CancellationAwareHttpClient;

const httpProvider = 
  provide: Http,
  deps: [ConnectionBackend, RequestOptions],
  useFactory: function (backend: ConnectionBackend, defaultOptions: RequestOptions) 
    if (!cancellationAwareClient) 
      const wrapped = new Http(backend, defaultOptions);
      cancellationAwareClient = new CancellationAwareHttpClient(wrappedHttp);
    
    return cancellationAwareClient;
  
;

@NgModule(
  providers: [
    // provide our service as `Http`, replacing the stock provider
    httpProvider,
    // provide the same instance of our service as `CancellationAwareHttpClient`
    // for those wanting access to `cancelOutstandingRequests`
    ...httpProvider, provide: CancellationAwareHttpClient
  ]
) export class SomeModule 

请注意我们如何覆盖现有框架提供的服务。我们使用工厂来创建我们的实例,并且不向包装器本身添加任何 DI 装饰器,以避免注入器中的循环。

【讨论】:

我想实现这一点,因为即使在我浏览页面时也是如此。未决的 HTTP 请求不会被取消。我必须在每个页面上使用销毁订阅。我想为什么不在全局范围内进行,即在路由更改期间取消所有未决的 HTTP 请求。 纠正我如果我想实现的目标是错误的:) @SibiRaj 我不认为这是错误的 :) 在引入全局状态时要小心。这种方法的好处是您可以在不更改任何使用Http 的服务或组件的情况下进行试验。 嗨@AluanHaddad,你从哪里得到wrappedHttp?你的评论没有澄清这一点。你能和我们分享更多的细节吗?谢谢 @Sibiraj 我直接实例化了它。它在示例中,但代码可能需要稍作调整才能在角度 6/7 中工作【参考方案5】:

ngOnDestroy 回调通常用于在实例被销毁时需要进行的任何自定义清理。

您想在哪里取消您的请求?

也许如果你想在浏览器关闭时取消你的请求,有创意here

【讨论】:

我想取消路线变更请求 你应该在可能是路由器出口的组件上调用 ngOnDestroy() 所以我应该单独处理所有组件? 唯一可以放置在路由器插座上的组件。因为当路由器插座发生变化时,“ngOnDestroy()”被调用了。【参考方案6】:

试试这个:

import  Component, OnInit, OnDestroy  from '@angular/core';
import  Subscription  from 'rxjs/Rx';

export class Component implements OnInit, OnDestroy 
    private subscription: Subscription;
    ngOnInit() 
        this.subscription = this.route.params.subscribe();
    
    ngOnDestroy() 
        this.subscription.unsubscribe();
    

【讨论】:

不,这不会取消挂起的请求。 是的,我想一次性取消。即,当路线改变时【参考方案7】:
    //This is the example of cancelling the get request once you leave the TestComponent.

    import  Component, OnInit from '@angular/core';

    @Component(
      selector: 'app-test',
      templateUrl: './test.component.html'
    )
    export class TestComponent implements OnInit 

      request: any;
someList: any;

      constructor( private _someService: SomeService) 

      

    ngOnInit() 
        this.getList();
      

      ngOnDestroy()
        this.request.unsubscribe(); // To cancel the get request.
      

      getList() 
        this.request= this._someService.getAll()
          .subscribe((response: any) => 
            this.someList= response;
          , (error) => 
            console.log("Error fetching List", error);
          )
      

    

【讨论】:

【参考方案8】:

您可以创建一个自定义的 Http 服务(使用 HttpClient)来维护一个待处理请求的列表。每当您使用此自定义服务而不是 Http/HttpClient 触发 http 时,现在将订阅推送到列表并在响应返回时弹出该订阅。使用它,您将在列表中拥有所有不完整的订阅。

现在在同一个自定义服务的构造函数中注入路由器并订阅它以获取路由更改事件。现在,每当这个 observable 发出时,您需要做的就是取消订阅列表中存在的所有订阅并从中弹出所有元素。

如果您需要代码 sn-p,请在评论中提及。

【讨论】:

【参考方案9】:

在@Bladito 答案中添加一些内容,这几乎是完美的。

实际上,HttpCancelService 堆栈是完美的,但问题在于它的调用位置。如果您有子路线,在导航结束时调用它可能会导致问题。

所以我制作了一个抽象容器组件,它在销毁时调用 HttpCancelService。这样我就可以在我想用更细的粒度削减任何 Http Canceling 请求时进行管理。

import  Component, OnDestroy, OnInit  from '@angular/core';
import  HttpCancelService  from '../../services/http-cancel-service.service';

@Component(
  selector: 'some-abstract-container',
  template: `
    ABSTRACT COMPONENT
  `,
  styleUrls: ['./abstract-container.component.scss']
)
export class AbstractContainerComponent implements OnInit, OnDestroy 
  constructor(protected readonly httpCancelService: HttpCancelService) 

  ngOnInit() 

  ngOnDestroy(): void 
    this.httpCancelService.cancelPendingRequests();
  



还有一个扩展抽象组件的具体组件:

import  Component, OnInit  from '@angular/core';
import  AbstractContainerComponent  from '../../../shared/components/abstract-container/abstract-container.component';
import  HttpCancelService  from '../../../shared/services/http-cancel-service.service';

@Component(
  selector: 'some-concrete-container',
  templateUrl: '.some-concrete-container.component.html',
  styleUrls: ['./some-concrete-container.component.scss']
)
export class SomeConcreteContainerComponent extends AbstractContainerComponent implements OnInit 
  constructor(protected readonly httpCancelService: HttpCancelService) 
    super(httpCancelService);
  

  ngOnInit() 


【讨论】:

【参考方案10】:

我认为取消路由更改级别的请求不是一个好主意,因为这样会失去粒度。

例如,也许您想取消一个组件而不是另一个组件的请求,因为它不会破坏。最重要的是,后台请求呢?调试一些请求被随机取消的原因将非常棘手。

但通常最好取消 get 其组件将被破坏的请求,无论路由更改如何


在销毁时取消订阅 observables

如果您想让您的生活更轻松,请使用until-destroy。当你的组件即将被销毁时,它会自动取消订阅所有的 observables (ngOnDestroy)。 它足够细化,更通用(不仅是 HttpRequests,而且所有 observables 都将被取消订阅)

import  UntilDestroy, untilDestroyed  from '@ngneat/until-destroy';
     
@UntilDestroy()
@Component()
export class InboxComponent 
  ngOnInit() 
    interval(1000)
      .pipe(untilDestroyed(this))
      .subscribe();
  

【讨论】:

以上是关于如何在 Angular 4+ 中取消/取消订阅所有挂起的 HTTP 请求的主要内容,如果未能解决你的问题,请参考以下文章

如果已经使用相同的请求运行,则取消订阅/取消 Angular 4 中的现有 HTTP/XHR 调用

如何取消订阅由 Angular 服务创建的 observable [重复]

我应该取消订阅根 Angular 组件中的 observables 吗?

我需要手动取消订阅吗? - Angular 8 [重复]

angular中全局订阅事件,包含取消订阅,非常好用

在非组件/指令类中取消订阅 Observable