Ist es notwendig, Observables, die mit HTTP-Methoden erstellt wurden, abzubestellen?

211

Müssen Sie sich von Angular 2 http-Aufrufen abmelden, um Speicherverluste zu vermeiden?

 fetchFilm(index) {
        var sub = this._http.get(`http://example.com`)
            .map(result => result.json())
            .map(json => {
                dispatch(this.receiveFilm(json));
            })
            .subscribe(e=>sub.unsubscribe());
            ...
born2net
quelle
1
Mögliches Duplikat von Speicherlecks in Angular 2 verhindern?
Eric Martinez
1
Siehe stackoverflow.com/questions/34461842/… (und auch die Kommentare)
Eric Martinez

Antworten:

253

Die Antwort lautet also nein, das tust du nicht. Ng2wird es selbst aufräumen.

Die HTTP-Dienstquelle aus Angulars HTTP-XHR-Backend-Quelle:

Geben Sie hier die Bildbeschreibung ein

Beachten Sie, wie es ausgeführt wird, complete()nachdem Sie das Ergebnis erhalten haben. Dies bedeutet, dass es sich nach Abschluss tatsächlich abmeldet. Sie müssen es also nicht selbst tun.

Hier ist ein Test zur Validierung:

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

Wie erwartet können Sie sehen, dass es immer automatisch abgemeldet wird, nachdem Sie das Ergebnis erhalten und mit den beobachtbaren Operatoren fertig sind. Dies geschieht bei einer Zeitüberschreitung (Nr. 3), damit wir den Status des Observablen überprüfen können, wenn alles erledigt und abgeschlossen ist.

Und das Ergebnis

Geben Sie hier die Bildbeschreibung ein

Es würde also kein Leck existieren, wenn Sie sich Ng2automatisch abmelden!

Nizza zu erwähnen: Dies Observablewird kategorisiert, da finiteim Gegensatz zu dem, infinite Observablewas ein unendlicher Datenstrom ist, wie clickzum Beispiel DOM- Listener ausgegeben werden kann .

DANKE, @rubyboy für Hilfe dazu.

born2net
quelle
17
Was passiert in den Fällen, in denen der Benutzer die Seite verlässt, bevor die Antwort eingeht, oder wenn die Antwort nie eingeht, bevor der Benutzer die Ansicht verlässt? Würde das nicht ein Leck verursachen?
Thibs
1
Ich würde das auch gerne oben wissen.
1984
4
@ 1984 Die Antwort lautet IMMER ABONNIEREN. Diese Antwort ist aus genau dem Grund, der in diesem Kommentar angesprochen wird, völlig falsch. Der Benutzer, der weg navigiert, ist eine Speicherverbindung. Wenn der Code in diesem Abonnement ausgeführt wird, nachdem der Benutzer weg navigiert ist, kann dies zu Fehlern / unerwarteten Nebenwirkungen führen. Ich benutze takeWhile (() => this.componentActive) für alle Observablen und setze this.componentActive = false in ngOnDestroy, um alle Observablen in einer Komponente zu bereinigen.
Lenny
4
Das hier gezeigte Beispiel zeigt eine tiefe eckige private API. Bei jeder anderen privaten API kann sich dies mit einem Upgrade ohne vorherige Ankündigung ändern. Als Faustregel gilt: Wenn es nicht offiziell dokumentiert ist, machen Sie keine Annahmen. Beispielsweise verfügt das AsynPipe über eine übersichtliche Dokumentation, die es automatisch für Sie abonniert / abbestellt. Die HttpClient-Dokumente erwähnen dazu nichts.
YoussefTaghlabi
1
@YoussefTaghlabi, FYI, in der offiziellen Winkeldokumentation ( angle.io/guide/http ) wird Folgendes erwähnt: "Die AsyncPipe abonniert (und abbestellt) automatisch für Sie." Es ist also tatsächlich offiziell dokumentiert.
Hudgi
96

Worüber redet ihr Leute !!!

OK, es gibt zwei Gründe, sich von einem beobachtbaren Objekt abzumelden. Niemand scheint viel über den sehr wichtigen zweiten Grund zu sprechen!

1) Ressourcen bereinigen. Wie andere gesagt haben, ist dies ein vernachlässigbares Problem für HTTP-Observable. Es wird sich einfach selbst aufräumen.

2) Verhindern Sie, dass der subscribeHandler ausgeführt wird.

(Bei HTTP wird dadurch auch die Anforderung im Browser abgebrochen, sodass keine Zeit mit dem Lesen der Antwort verschwendet wird. Dies ist jedoch eine Ausnahme von meinem Hauptpunkt unten.)

Die Relevanz von Nummer 2 hängt davon ab, was Ihr Abonnement-Handler tut:

Wenn Ihre subscribe()Handlerfunktion eine unerwünschte Nebenwirkung hat, wenn ein Aufruf geschlossen oder entsorgt wird, müssen Sie sich abmelden (oder eine bedingte Logik hinzufügen), um zu verhindern, dass sie ausgeführt wird.

Betrachten Sie einige Fälle:

1) Ein Anmeldeformular. Sie geben Benutzername und Passwort ein und klicken auf "Anmelden". Was passiert, wenn der Server langsam ist und Sie sich entscheiden, Escape zu drücken, um den Dialog zu schließen? Sie werden wahrscheinlich annehmen, dass Sie nicht angemeldet waren, aber wenn die http-Anforderung nach dem Drücken von Escape zurückgegeben wurde, führen Sie trotzdem die dort vorhandene Logik aus. Dies kann dazu führen, dass eine Weiterleitung zu einer Kontoseite, ein unerwünschtes Anmeldecookie oder eine Tokenvariable festgelegt wird. Dies ist wahrscheinlich nicht das, was Ihr Benutzer erwartet hat.

2) Ein Formular zum Senden einer E-Mail.

Wenn der subscribeHandler für "sendEmail" beispielsweise eine Animation "Ihre E-Mail wird gesendet" auslöst, Sie auf eine andere Seite weiterleitet oder versucht, auf alles zuzugreifen, was entsorgt wurde, können Ausnahmen oder unerwünschtes Verhalten auftreten.

Achten Sie auch darauf, nicht anzunehmen, unsubscribe()dass "Abbrechen" bedeutet. Sobald die HTTP-Nachricht im Flug ist, unsubscribe()wird die HTTP-Anforderung NICHT abgebrochen, wenn sie bereits Ihren Server erreicht hat. Die Antwort, die Sie erhalten, wird nur abgebrochen. Und die E-Mail wird wahrscheinlich gesendet.

Wenn Sie das Abonnement erstellen, um die E-Mail direkt in einer UI-Komponente zu senden, möchten Sie sich wahrscheinlich bei der Entsorgung abmelden. Wenn die E-Mail jedoch von einem nicht UI-zentralisierten Dienst gesendet wird, müssen Sie dies wahrscheinlich nicht tun.

3) Eine Winkelkomponente, die zerstört / geschlossen wird. Alle http-Observables, die zu diesem Zeitpunkt noch ausgeführt werden, werden vervollständigt und ihre Logik ausgeführt, sofern Sie sich nicht abmelden onDestroy(). Ob die Konsequenzen trivial sind oder nicht, hängt davon ab, was Sie im Abonnement-Handler tun. Wenn Sie versuchen, etwas zu aktualisieren, das nicht mehr existiert, wird möglicherweise eine Fehlermeldung angezeigt.

Manchmal haben Sie möglicherweise einige Aktionen, die Sie möchten, wenn die Komponente entsorgt wird, und einige, die Sie nicht möchten. Zum Beispiel haben Sie vielleicht einen Swoosh-Sound für eine gesendete E-Mail. Sie möchten wahrscheinlich, dass dies auch dann abgespielt wird, wenn die Komponente geschlossen wurde. Wenn Sie jedoch versuchen, eine Animation für die Komponente auszuführen, schlägt dies fehl. In diesem Fall wäre eine zusätzliche bedingte Logik innerhalb von subscribe die Lösung - und Sie möchten das beobachtbare http NICHT abbestellen.

Als Antwort auf die eigentliche Frage müssen Sie dies nicht tun, um Speicherlecks zu vermeiden. Sie müssen dies jedoch (häufig) tun, um zu vermeiden, dass unerwünschte Nebenwirkungen durch das Ausführen von Code ausgelöst werden, der Ausnahmen auslösen oder Ihren Anwendungsstatus beschädigen kann.

Tipp: Die Subscriptionenthält eine closedboolesche Eigenschaft, die in fortgeschrittenen Fällen hilfreich sein kann. Für HTTP wird dies festgelegt, wenn es abgeschlossen ist. In Angular kann es in einigen Situationen hilfreich sein, eine _isDestroyedEigenschaft festzulegen, in ngDestroyder Ihr subscribeHandler sie überprüfen kann .

Tipp 2: Wenn Sie mehrere Abonnements bearbeiten, können Sie ein Ad-hoc- new Subscription()Objekt und add(...)alle anderen Abonnements erstellen. Wenn Sie sich also vom Hauptobjekt abmelden, werden auch alle hinzugefügten Abonnements abgemeldet.

Simon_Weaver
quelle
2
Beachten Sie außerdem, dass Sie, wenn Sie einen Dienst haben, der das rohe http-Overvable zurückgibt und es dann vor dem Abonnieren weiterleitet, nur das endgültige Observable abbestellen müssen, nicht das zugrunde liegende http-Observable. Tatsächlich haben Sie nicht einmal ein Abonnement für http direkt, also können Sie nicht.
Simon_Weaver
Auch wenn das Abbestellen die Browseranforderung abbricht, reagiert der Server dennoch auf die Anforderung. Gibt es eine Möglichkeit, den Server zu intimieren, um den Vorgang ebenfalls abzubrechen? Ich versende schnell hintereinander http-Anfragen, von denen die meisten durch Abbestellen storniert werden. Der Server verarbeitet jedoch weiterhin die vom Client stornierten Anforderungen, wodurch die legitime Anforderung wartet.
Bala
@bala Sie müssten sich dafür einen eigenen Mechanismus einfallen lassen - der nichts mit RxJS zu tun hätte. Zum Beispiel können Sie die Anforderungen einfach in eine Tabelle einfügen und sie nach 5 Sekunden im Hintergrund ausführen. Wenn etwas abgebrochen werden muss, löschen oder setzen Sie einfach ein Flag für die älteren Zeilen, das die Ausführung stoppt. Würde ganz davon abhängen, was Ihre Bewerbung ist. Da Sie jedoch erwähnen, dass der Server blockiert, ist er möglicherweise so konfiguriert, dass jeweils nur eine Anforderung zulässig ist. Dies hängt jedoch wiederum davon ab, was Sie verwendet haben.
Simon_Weaver
🔥🔥🔥Tipp 2 - ist ein PRO-TIPP 🔥🔥🔥 für alle, die sich von mehreren Abonnements abmelden möchten, indem sie nur eine Funktion aufrufen. Fügen Sie mit der Methode .add () und als .unsubscribe () in ngDestroy hinzu.
Abhay Tripathi
23

Das Aufrufen der unsubscribeMethode dient eher dazu, eine laufende HTTP-Anforderung abzubrechen, da diese Methode die abortfür das zugrunde liegende XHR-Objekt aufruft und Listener für die Lade- und Fehlerereignisse entfernt:

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

Das heißt, unsubscribeentfernt Zuhörer ... Es könnte also eine gute Idee sein, aber ich denke nicht, dass es für eine einzelne Anfrage notwendig ist ;-)

Hoffe es hilft dir, Thierry

Thierry Templier
quelle
: | das ist absurd: | Ich suchte nach einem Weg, um aufzuhören ... aber ich wunderte mich, und wenn ich es in einem Szenario programmieren würde: Ich würde es so machen: y = x.subscribe((x)=>data = x); Dann änderte ein Benutzer die Eingaben und x.subscribe((x)=>cachlist[n] = x); y.unsubscribe()hey, Sie haben unsere Serverressourcen verwendet, ich habe gewonnen Wirf dich nicht weg ..... und in einem anderen Szenario: ruf einfach y.stop()alles wegwerfen
deadManN
16

Das Abbestellen ist ein MUSS, wenn Sie ein deterministisches Verhalten für alle Netzwerkgeschwindigkeiten wünschen .

Stellen Sie sich vor , Komponente A wird in einer Registerkarte gerendert. Klicken Sie auf eine Schaltfläche, um eine GET-Anfrage zu senden. Es dauert 200 ms, bis die Antwort zurückkommt. Sie können die Registerkarte also jederzeit schließen, wenn Sie wissen, dass der Computer schneller als Sie ist und die http-Antwort verarbeitet und abgeschlossen ist, bevor die Registerkarte geschlossen und Komponente A zerstört wird.

Wie wäre es mit einem sehr langsamen Netzwerk? Wenn Sie auf eine Schaltfläche klicken, dauert es 10 Sekunden, bis die 'GET'-Anforderung ihre Antwort erhält. Nach 5 Sekunden Wartezeit entscheiden Sie sich jedoch, die Registerkarte zu schließen. Dadurch wird die Komponente A zerstört, die zu einem späteren Zeitpunkt gesammelt werden soll. Warte eine Minute! , wir haben uns nicht abgemeldet - jetzt, 5 Sekunden später, kommt eine Antwort zurück und die Logik in der zerstörten Komponente wird ausgeführt. Diese Ausführung wird jetzt in Betracht gezogen out-of-contextund kann zu vielen Dingen führen, einschließlich einer sehr geringen Leistung.

Daher takeUntil()empfiehlt es sich, http-Aufrufe zu verwenden und abzubestellen, wenn die Komponente zerstört wird.

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
  }
}
un33k
quelle
Ist es in Ordnung zu verwenden , BehaviorSubject()statt und rufen Sie einfach complete()in ngOnDestroy()?
Saksham
Sie müssten sich immer noch abmelden ... warum sollte BehaviourSubjectdas anders sein?
Ben Taliadoros
Es ist nicht erforderlich, sich von HttpClient-Observablen abzumelden, da es sich um endliche Observablen handelt, dh sie werden nach dem Ausgeben eines Werts abgeschlossen.
Yatharth Varshney
Sie sollten sich trotzdem abmelden, vorausgesetzt, es gibt einen Interceptor zum Anstoßen von Fehlern, aber Sie haben die Seite, die den Fehler ausgelöst hat, bereits verlassen. Auf einer anderen Seite wird eine Fehlermeldung angezeigt. Denken Sie auch an eine Seite zur Überprüfung des Zustands ruft alle Microservices an ... und so weiter
Washington Guedes
12

Auch beim neuen HttpClient-Modul bleibt das gleiche Verhalten erhalten Pakete / common / http / src / jsonp.ts

Ricardo Martínez
quelle
Der obige Code scheint aus einem Komponententest zu stammen, was bedeutet, dass Sie mit HttpClient .complete () selbst aufrufen müssen ( github.com/angular/angular/blob/… )
CleverPatrick
Ja, Sie haben Recht, aber wenn Sie ( github.com/angular/angular/blob/… ) inspizieren, werden Sie dasselbe sehen. In Bezug auf die Hauptfrage, falls erforderlich, explizit abzumelden? die meisten Fälle nein, da wird von Angular selbst behandelt. Dies könnte ein Fall sein, in dem eine lange Antwort stattfinden könnte und Sie zu einer anderen Route gewechselt sind oder eine Komponente zerstört haben. In diesem Fall haben Sie die Möglichkeit, auf etwas zuzugreifen, das nicht mehr existiert, und eine Ausnahme auszulösen.
Ricardo Martínez
8

Sie sollten sich nicht von Observables abmelden, die automatisch abgeschlossen werden (z. B. HTTP, Aufrufe). Aber es ist notwendig, sich von unendlichen Observablen wie abzumelden Observable.timer().

Mykhailo Mykhaliuk
quelle
6

Lesen Sie nach einer Weile des Testens die Dokumentation und den Quellcode des 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 Univeristy: https://blog.angular-university.io/angular-http/

Diese bestimmte Art von Observables sind einwertige Streams: Wenn die HTTP-Anforderung erfolgreich ist, geben diese Observables nur einen Wert aus und werden dann abgeschlossen

Und die Antwort auf die gesamte Ausgabe von "MUSS ich" abbestellen?

Es hängt davon ab, ob. HTTP-Aufruf Memoryleaks sind kein Problem. Die Probleme sind die Logik in Ihren Rückruffunktionen.

Zum Beispiel: Routing oder Login.

Wenn es sich bei Ihrem Anruf um einen Anmeldeanruf handelt, müssen Sie sich nicht abmelden. Sie müssen jedoch sicherstellen, dass Sie die Antwort in Abwesenheit des Benutzers ordnungsgemäß verarbeiten, wenn der Benutzer die Seite verlässt.


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

Von nervig bis gefährlich

Stellen Sie sich vor, das Netzwerk ist langsamer als gewöhnlich, der Anruf dauert länger als 5 Sekunden, und der Benutzer verlässt die Anmeldeansicht und wechselt zu einer "Support-Ansicht".

Die Komponente ist möglicherweise nicht aktiv, aber das Abonnement. Im Falle einer Antwort wird der Benutzer plötzlich umgeleitet (abhängig von Ihrer Implementierung von handleResponse ()).

Das ist nicht gut

Stellen Sie sich auch vor, der Benutzer verlässt den PC und glaubt, noch nicht angemeldet zu sein. Aber Ihre Logik meldet den Benutzer an, jetzt haben Sie ein Sicherheitsproblem.

Was können Sie tun, ohne sich abzumelden?

Machen Sie Ihren Anruf abhängig vom aktuellen Status der Ansicht:

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

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

Benutzer .pipe(takeWhile(value => this.isActive)), um sicherzustellen, dass die Antwort nur verarbeitet wird, wenn die Ansicht aktiv ist.


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

Aber wie können Sie sicher sein, dass das Abonnement keine Speicherlecks verursacht?

Sie können protokollieren, wenn "teardownLogic" angewendet wird.

Die TeardownLogic eines Abonnements wird aufgerufen, wenn das Abonnement leer oder nicht abonniert ist.


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

Sie müssen sich nicht abmelden. Sie sollten wissen, ob es Probleme in Ihrer Logik gibt, die Probleme in Ihrem Abonnement verursachen können. Und pass auf sie auf. In den meisten Fällen ist dies kein Problem. Insbesondere bei kritischen Aufgaben wie der Autorisierung sollten Sie sich jedoch um unerwartetes Verhalten kümmern, sei es mit "Abbestellen" oder einer anderen Logik wie Piping- oder bedingten Rückruffunktionen.

warum nicht einfach immer abbestellen?

Stellen Sie sich vor, Sie stellen eine Put- oder Post-Anfrage. Der Server empfängt die Nachricht so oder so, nur die Antwort dauert eine Weile. Wenn Sie sich abmelden, wird der Beitrag nicht rückgängig gemacht oder abgelegt. Wenn Sie sich jedoch abmelden, haben Sie nicht die Möglichkeit, die Antwort zu bearbeiten oder den Benutzer zu informieren, z. B. über einen Dialog oder einen Toast / eine Nachricht usw.

Dies lässt den Benutzer glauben, dass die Put / Post-Anfrage nicht ausgeführt wurde.

Es kommt also darauf an. Es ist Ihre Entwurfsentscheidung, wie Sie mit solchen Problemen umgehen.

Luxusproblem
quelle
4

Sie sollten diesen Artikel unbedingt lesen . Es zeigt Ihnen, warum Sie sich auch von http immer abmelden sollten .

Wenn Sie nach dem Erstellen der Anforderung, jedoch vor dem Erhalt einer Antwort vom Back-End, die Komponente für unnötig halten und zerstören, behält Ihr Abonnement den Verweis auf die Komponente bei, wodurch die Möglichkeit besteht, Speicherlecks zu verursachen.

Aktualisieren

Die obige Bestätigung scheint wahr zu sein, aber wenn die Antwort zurückkommt, wird das http-Abonnement trotzdem zerstört

MTZ
quelle
1
Ja, und auf der Netzwerkregisterkarte Ihres Browsers wird tatsächlich ein rotes "Abbrechen" angezeigt, damit Sie sicher sein können, dass es funktioniert.
Simon_Weaver
-2

RxJS Observable sind grundsätzlich zugeordnet und funktionieren entsprechend, wenn Sie es abonnieren. Wenn wir das Observable erstellen und die Bewegung, die wir vervollständigen, wird Observable automatisch geschlossen und abgemeldet.

Sie arbeiten auf die gleiche Weise wie die Beobachter, aber in einer ganz anderen Reihenfolge. Die bessere Vorgehensweise, um sie abzubestellen, wenn eine Komponente zerstört wird. Wir könnten dies anschließend tun, indem wir z. this. $ manageSubscription.unsubscibe ()

Wenn wir die beobachtbare Syntax wie unten erwähnt erstellt haben, wie z

** return new Observable ((Beobachter) => {** // Es macht beobachtbar im kalten Zustand ** Observer.complete () **}) **

Sachin Mishra
quelle