È necessario annullare l'iscrizione agli osservabili creati con i metodi Http?


211

È necessario annullare l'iscrizione alle chiamate http Angular 2 per evitare perdite di memoria?

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


Risposte:


253

Quindi la risposta è no, tu no. Ng2lo pulirà da solo.

La fonte del servizio Http, dalla fonte back-end XHR Http di Angular:

inserisci qui la descrizione dell'immagine

Notare come viene eseguito complete()dopo aver ottenuto il risultato. Ciò significa che in realtà annulla l'iscrizione al completamento. Quindi non è necessario farlo da soli.

Ecco un test per convalidare:

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

Come previsto, puoi vedere che viene sempre annullato automaticamente dopo aver ottenuto il risultato e aver terminato con gli operatori osservabili. Questo accade in un timeout (n. 3) in modo da poter controllare lo stato dell'osservabile quando tutto è terminato e completato.

E il risultato

inserisci qui la descrizione dell'immagine

Quindi, non esisterebbe alcuna perdita come Ng2annullamento automatico dell'iscrizione!

Bello da menzionare: questo Observableè classificato come finite, al contrario del infinite Observablequale è un flusso infinito di dati che può essere emesso come clicklistener DOM, ad esempio.

GRAZIE, @rubyboy per aiuto su questo.


17
cosa succede nei casi in cui l'utente lascia la pagina prima che arrivi la risposta o se la risposta non arriva mai prima che l'utente lasci la vista? Ciò non causerebbe una perdita?
Thibs,

1
Vorrei sapere anche quanto sopra.
1984,

4
@ 1984 La risposta è SEMPRE UNSUBSCRIBE. Questa risposta è del tutto errata esattamente per il motivo sollevato in questo commento. L'utente che si allontana è un collegamento di memoria. Inoltre, se il codice in tale abbonamento viene eseguito dopo che l'utente si allontana, potrebbe causare errori / avere effetti collaterali imprevisti. Tendo a usare takeWhile (() => this.componentActive) su tutti gli osservabili e ho appena impostato this.componentActive = false in ngOnDestroy per ripulire tutti gli osservabili in un componente.
Lenny,

4
L'esempio visualizzato qui copre una api privata angolare profonda. con qualsiasi altra API privata, può cambiare con un aggiornamento senza preavviso. La regola empirica è: se non è documentata ufficialmente, non fare ipotesi. Ad esempio, AsynPipe ha una chiara documentazione che si iscrive / annulla automaticamente per te. I documenti di HttpClient non menzionano nulla al riguardo.
YoussefTaghlabi,

1
@YoussefTaghlabi, FYI, la documentazione angolare ufficiale ( angular.io/guide/http ) menziona che: "AsyncPipe si iscrive (e annulla l'iscrizione) automaticamente per te". Quindi, è davvero documentato ufficialmente.
Hudgi,

96

Di cosa state parlando !!!

OK, quindi ci sono due motivi per annullare l'iscrizione a qualsiasi osservabile. Nessuno sembra parlare molto della seconda ragione molto importante!

1) Pulisci risorse. Come altri hanno già detto, questo è un problema trascurabile per gli osservabili HTTP. Si pulirà da solo.

2) Impedire l' subscribeesecuzione del gestore.

(Per HTTP questo in realtà annullerà anche la richiesta nel browser, quindi non perderà tempo a leggere la risposta. Ma questo è in realtà un punto a parte il mio punto principale di seguito.)

La pertinenza del numero 2 dipenderà da ciò che fa il gestore della sottoscrizione:

Se la subscribe()funzione del gestore ha un qualsiasi tipo di effetto collaterale indesiderato se le chiamate vengono chiuse o eliminate, è necessario annullare l'iscrizione (o aggiungere la logica condizionale) per impedirne l'esecuzione.

Considera alcuni casi:

1) Un modulo di accesso. Inserisci nome utente e password e fai clic su "Accedi". Cosa succede se il server è lento e si decide di premere Esc per chiudere la finestra di dialogo? Probabilmente supponerai di non aver effettuato l'accesso, ma se la richiesta http è stata restituita dopo aver premuto Esc, eseguirai comunque la logica che hai lì. Ciò può comportare il reindirizzamento alla pagina di un account, l'impostazione di un cookie di accesso indesiderato o di una variabile token. Questo probabilmente non è quello che il tuo utente si aspettava.

2) Un modulo "invia e-mail".

Se il subscribegestore di "sendEmail" fa qualcosa come attivare un'animazione "La tua e-mail viene inviata", trasferirti in una pagina diversa o provare ad accedere a qualsiasi cosa che è stata eliminata, potresti ottenere eccezioni o comportamenti indesiderati.

Fai anche attenzione a non assumere unsubscribe()significa "annulla". Una volta che il messaggio HTTP è in volo unsubscribe()NON annullerà la richiesta HTTP se è già stata raggiunta dal tuo server. Annullerà solo la risposta che ti viene restituita. E probabilmente l'e-mail verrà inviata.

Se si crea l'abbonamento per inviare l'e-mail direttamente all'interno di un componente dell'interfaccia utente, probabilmente si vorrebbe annullare l'iscrizione al momento dell'eliminazione, ma se l'e-mail viene inviata da un servizio centralizzato non dell'interfaccia utente, probabilmente non è necessario.

3) Un componente angolare che viene distrutto / chiuso. Eventuali osservabili http ancora in esecuzione al momento verranno completati ed eseguiranno la loro logica a meno che non si annulli l'iscrizione onDestroy(). Il fatto che le conseguenze siano o meno banali dipenderà da ciò che fai nel gestore della sottoscrizione. Se provi ad aggiornare qualcosa che non esiste più potresti ricevere un errore.

A volte potresti avere alcune azioni che vorresti se il componente fosse eliminato, e altre no. Ad esempio, potresti avere un suono "swoosh" per un'email inviata. Probabilmente vorresti che questo venisse riprodotto anche se il componente fosse chiuso, ma se provi a eseguire un'animazione sul componente non funzionerebbe. In tal caso, la soluzione potrebbe essere una logica condizionale aggiuntiva all'interno dell'abbonamento e NON si desidera annullare l'iscrizione a http osservabile.

Quindi, in risposta alla domanda reale, no non è necessario farlo per evitare perdite di memoria. Ma è necessario farlo (spesso) per evitare che gli effetti collaterali indesiderati vengano attivati ​​eseguendo codice che può generare eccezioni o danneggiare lo stato dell'applicazione.

Suggerimento: Subscriptioncontiene una closedproprietà booleana che può essere utile nei casi avanzati. Per HTTP questo verrà impostato al completamento. In Angolare potrebbe essere utile in alcune situazioni impostare una _isDestroyedproprietà in ngDestroycui può essere verificato dal subscribegestore.

Suggerimento 2: se si gestiscono più abbonamenti è possibile creare un new Subscription()oggetto ad-hoc e add(...)qualsiasi altro abbonamento ad esso - quindi quando si annulla l'iscrizione a quello principale, verranno annullati anche tutti gli abbonamenti aggiunti.


2
Si noti inoltre che se si dispone di un servizio che restituisce il http non elaborabile e lo si collega prima di abbonarsi, è necessario annullare l'iscrizione solo dall'osservabile finale, non dal sottostante osservabile. In realtà non avrai nemmeno un abbonamento a http direttamente, quindi non puoi.
Simon_Weaver,

Anche se l'annullamento dell'iscrizione annulla la richiesta del browser, il server agisce comunque sulla richiesta. C'è un modo per intimidire il server per interrompere anche l'operazione? Sto inviando richieste http in rapida successione, di cui la maggior parte viene annullata cancellando l'iscrizione. Tuttavia, il server funziona ancora sulle richieste annullate dal client, causando l'attesa della richiesta legittima.
bala,

@bala dovresti trovare il tuo meccanismo per questo - che non avrebbe nulla a che fare con RxJS. Ad esempio, puoi semplicemente inserire le richieste in una tabella ed eseguirle dopo 5 secondi in background, quindi se qualcosa dovesse essere annullato elimineresti o imposteresti un flag sulle righe più vecchie che ne impedirebbe l'esecuzione. Dipenderebbe completamente da quale sia l'applicazione. Tuttavia, poiché si dice che il server sta bloccando, potrebbe essere configurato per consentire solo una richiesta alla volta, ma ciò dipende nuovamente da ciò che si stava utilizzando.
Simon_Weaver,

IpTipo 2 - è un PRO-TIP, 🔥🔥🔥 per chiunque desideri annullare l'iscrizione a più abbonamenti chiamando una sola funzione. Aggiungi usando il metodo .add () e poi .unsubscribe () in ngDestroy.
abhay tripathi,

23

Chiamare il unsubscribemetodo significa piuttosto annullare una richiesta HTTP in corso poiché questo metodo chiama abortquello sull'oggetto XHR sottostante e rimuove i listener sugli eventi load ed error:

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

Detto questo, unsubscriberimuove gli ascoltatori ... Quindi potrebbe essere una buona idea ma non penso che sia necessario per una singola richiesta ;-)

Spero che ti aiuti, Thierry


: | è assurdo: | stavo cercando un modo per fermarmi ... ma mi chiedevo, e se fossi io a scrivere codice, in qualche scenario: lo farei così: y = x.subscribe((x)=>data = x); poi un utente cambia gli input e x.subscribe((x)=>cachlist[n] = x); y.unsubscribe()hey hai usato le nostre risorse del server, ho vinto ti butto via ..... e in altri scenari: basta chiamare y.stop()butta via tutto
deadManN

16

L'annullamento dell'iscrizione è DOVUTO se si desidera un comportamento deterministico su tutte le velocità della rete.

Immagina che il componente A sia visualizzato in una scheda: fai clic su un pulsante per inviare una richiesta "OTTIENI". Sono necessari 200 ms perché la risposta torni. Quindi, puoi tranquillamente chiudere la scheda in qualsiasi momento sapendo che la macchina sarà più veloce di te e la risposta http verrà elaborata ed è completa prima che la scheda venga chiusa e il componente A venga distrutto.

Che ne dici di una rete molto lenta? Fai clic su un pulsante, la richiesta "OTTIENI" impiega 10 secondi per ricevere la sua risposta, ma 5 secondi per l'attesa decidi di chiudere la scheda. Ciò distruggerà il componente A da raccogliere in un secondo momento. Apetta un minuto! , non abbiamo annullato l'iscrizione. Ora, 5 secondi dopo, viene restituita una risposta e la logica nel componente distrutto verrà eseguita. Tale esecuzione è ora considerata out-of-contexte può comportare molte cose, tra cui prestazioni molto basse.

Pertanto, è consigliabile utilizzare takeUntil()e annullare l'iscrizione alle chiamate http quando il componente viene distrutto.

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
  }
}

Va bene usare BehaviorSubject()invece e chiamare appena complete()dentro ngOnDestroy()?
Saksham,

Dovresti comunque annullare l'iscrizione ... perché sarebbe BehaviourSubjectdiverso?
Ben Taliadoros,

Non è necessario annullare l'iscrizione agli osservabili di HttpClient poiché sono osservabili finiti, ovvero si completano dopo aver emesso un valore.
Yatharth Varshney,

Dovresti comunque annullare l'iscrizione, supponiamo che ci sia un intercettore per tostare gli errori, ma hai già lasciato la pagina che ha attivato l'errore .. vedrai un messaggio di errore in un'altra pagina .... Pensa anche a una pagina di controllo dello stato che chiama tutti i microservizi ... e così via
Washington Guedes il

12

Anche con il nuovo modulo HttpClient, rimane lo stesso comportamento pacchetti / common / http / src / jsonp.ts


Il codice sopra sembra provenire da un test unitario, il che implica che con HttpClient, è necessario chiamare .complete () da soli ( github.com/angular/angular/blob/… )
CleverPatrick

Sì, hai ragione, ma se controlli ( github.com/angular/angular/blob/… ) vedrai lo stesso. Per quanto riguarda la domanda principale, se necessario annullare esplicitamente l'iscrizione? la maggior parte dei casi no, poiché sarà gestita dalla stessa Angular. Potrebbe essere un caso in cui potrebbe aver luogo una lunga risposta e ti sei spostato su un'altra rotta o forse hai distrutto un componente, nel qual caso hai la possibilità di provare ad accedere a qualcosa che non esiste più sollevando un'eccezione.
Ricardo Martínez,

8

Non devi annullare l'iscrizione agli osservabili che si completano automaticamente (ad es. Http, chiamate). Ma è necessario annullare l'iscrizione a infiniti osservabili come Observable.timer().


6

Dopo un po 'di test, leggere la documentazione e il codice sorgente di 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/

Università angolare: https://blog.angular-university.io/angular-http/

Questo particolare tipo di osservabili sono flussi a valore singolo: se la richiesta HTTP ha esito positivo, questi osservabili emettono un solo valore e quindi completano

E la risposta all'intera questione di "HO BISOGNO" di annullare l'iscrizione?

Dipende. Chiamate HTTP I fogli di memoria non rappresentano un problema. I problemi sono la logica delle funzioni di callback.

Ad esempio: Routing o Login.

Se la tua chiamata è una chiamata di accesso, non devi "annullare l'iscrizione" ma devi assicurarti che se l'utente lascia la pagina, gestisci correttamente la risposta in assenza dell'utente.


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

Da fastidioso a pericoloso

Ora immagina, la rete è più lenta del solito, la chiamata impiega più di 5 secondi e l'utente lascia la vista di accesso e passa a una "vista di supporto".

Il componente potrebbe non essere attivo ma l'abbonamento. In caso di risposta, l'utente verrà reindirizzato all'improvviso (a seconda dell'implementazione di handleResponse ()).

Questo non va bene.

Immagina anche che l'utente lasci il pc, credendo di non aver ancora effettuato l'accesso. Ma la tua logica accede l'utente, ora hai un problema di sicurezza.

Cosa puoi fare SENZA annullare l'iscrizione?

Ti fanno chiamare in base allo stato corrente della vista:

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

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

L'utente .pipe(takeWhile(value => this.isActive))deve assicurarsi che la risposta sia gestita solo quando la vista è attiva.


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

Ma come puoi essere sicuro che l'abbonamento non stia causando i fogli di memoria?

Puoi registrare se viene applicato "teardownLogic".

Il log teardown di una sottoscrizione verrà chiamato quando la sottoscrizione è vuota o non iscritta.


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

Non è necessario annullare l'iscrizione. Dovresti sapere se ci sono problemi nella tua logica, che potrebbero causare problemi nel tuo abbonamento. E prenditi cura di loro. Nella maggior parte dei casi, non sarà un problema, ma soprattutto in compiti critici come l'autorizzazione, dovresti prenderti cura di comportamenti imprevisti, collegandoli con "annulla iscrizione" o un'altra logica come il piping o le funzioni di callback condizionale.

perché non sempre annullare l'iscrizione?

Immagina di fare una richiesta put o post. Il server riceve il messaggio in entrambi i modi, solo la risposta richiede un po 'di tempo. Annullamento iscrizione, non annullerà il post o mettere. Ma quando annulli l'iscrizione, non avrai la possibilità di gestire la risposta o informare l'utente, ad esempio tramite una finestra di dialogo o un brindisi / messaggio, ecc.

Che induce l'utente a credere che la richiesta put / post non sia stata eseguita.

Quindi dipende. È la tua decisione di progettazione, come affrontare tali problemi.


4

Dovresti assolutamente leggere questo articolo. Ti mostra perché dovresti sempre annullare l'iscrizione anche da http .

Se dopo aver creato la richiesta ma prima di ricevere una risposta dal back-end ritieni che il componente non sia necessario e lo distrugga, l'abbonamento manterrà il riferimento al componente creando così una possibilità di causare perdite di memoria.

Aggiornare

L'affermazione di cui sopra sembra essere vera, ma comunque, quando la risposta ritorna, l'abbonamento http viene comunque distrutto


1
Sì, in realtà vedrai un 'Annulla' rosso nella scheda di rete del tuo browser in modo da poter essere sicuro che funzioni.
Simon_Weaver

-2

RxJS osservabili sono sostanzialmente associati e funzionano di conseguenza lo abbonati. Quando creiamo l'osservabile e il movimento lo completiamo, l'osservabile viene automaticamente chiuso e cancellato.

Funzionano allo stesso modo degli osservatori, ma in un ordine abbastanza diverso. La migliore pratica per cancellarli quando il componente viene distrutto. In seguito potremmo farlo ad es. questo. $ manageSubscription.unsubscibe ()

Se abbiamo creato la sintassi osservabile come di seguito come

** return new Observable ((observer) => {** // Rende osservabile a freddo ** observer.complete () **}) **

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.