Czy konieczne jest anulowanie subskrypcji materiałów obserwowalnych utworzonych metodami HTTP?


211

Czy musisz anulować subskrypcję połączeń HTTP Angular 2, aby zapobiec wyciekom pamięci?

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


Odpowiedzi:


253

Więc odpowiedź brzmi nie, ty nie. Ng2sam to posprząta.

Źródło usługi HTTP, ze źródła zaplecza Http XHR Angulara:

wprowadź opis zdjęcia tutaj

Zauważ, jak to działa complete()po uzyskaniu wyniku. Oznacza to, że faktycznie wypisuje się po zakończeniu. Więc nie musisz tego robić sam.

Oto test sprawdzający poprawność:

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

Zgodnie z oczekiwaniami, możesz zobaczyć, że zawsze jest automatycznie anulowany po uzyskaniu wyniku i zakończeniu obserwowalnymi operatorami. Dzieje się to po przekroczeniu limitu czasu (# 3), dzięki czemu możemy sprawdzić status obserwowalnego, gdy wszystko zostanie wykonane i ukończone.

I wynik

wprowadź opis zdjęcia tutaj

Tak więc nie byłoby wycieku, ponieważ Ng2auto wypisuje się!

Miło wspomnieć: Observablejest to podzielone na kategorie, ponieważ finitew przeciwieństwie do tego, infinite Observablektóry jest nieskończonym strumieniem danych, może być emitowany jak clickna przykład odbiornik DOM .

DZIĘKI, @rubyboy za pomoc w tej sprawie.


17
co dzieje się w przypadkach, gdy użytkownik opuszcza stronę przed nadejściem odpowiedzi lub jeśli odpowiedź nigdy nie przychodzi, zanim użytkownik opuści widok? Czy to nie spowodowałoby wycieku?
Thibs,

1
Chciałbym również wiedzieć o powyższym.
1984

4
@ 1984 Odpowiedź brzmi ZAWSZE ANULUJ SIĘ. Ta odpowiedź jest całkowicie błędna z dokładnie tego powodu, który został podany w tym komentarzu. Użytkownik nawigujący poza jest linkiem do pamięci. Ponadto, jeśli kod w tej subskrypcji działa po tym, jak użytkownik opuści stronę, może to powodować błędy / mieć nieoczekiwane skutki uboczne. Zwykle używam takeWhile (() => this.componentActive) na wszystkich obserwowalnych obiektach i po prostu ustawiam this.componentActive = false w ngOnDestroy, aby wyczyścić wszystkie obserwowalne elementy.
Lenny

4
Przykład pokazany tutaj obejmuje prywatny kątowy api. z dowolnym innym prywatnym interfejsem API może się zmienić bez aktualizacji. Ogólna zasada brzmi: jeśli nie jest to oficjalnie udokumentowane, nie przyjmuj żadnych założeń. Na przykład AsynPipe ma przejrzystą dokumentację, że automatycznie subskrybuje / wypisuje się za Ciebie. Dokumenty HttpClient nie wspominają o tym.
YoussefTaghlabi

1
@YoussefTaghlabi, FYI, oficjalna dokumentacja kątowa ( angular.io/guide/http ) wspomina, że: „AsyncPipe subskrybuje (i wypisuje się) automatycznie”. Tak więc jest to oficjalnie udokumentowane.
Hudgi

96

O czym rozmawiacie!

OK, więc istnieją dwa powody, by zrezygnować z subskrypcji. Wydaje się, że nikt nie mówi dużo o bardzo ważnym drugim powodzie!

1) Oczyść zasoby. Jak powiedzieli inni, jest to znikomy problem dla obserwowalnych HTTP. Po prostu się oczyści.

2) Zapobiegnij uruchomieniu programu subscribeobsługi.

(W przypadku HTTP to faktycznie anuluje również żądanie w przeglądarce - więc nie będzie marnować czasu na czytanie odpowiedzi. Ale to właściwie pomijając mój główny punkt poniżej).

Trafność numeru 2 będzie zależeć od tego, co robi program obsługi subskrypcji:

Jeśli subscribe()funkcja modułu obsługi wywołuje jakikolwiek efekt uboczny, który jest niepożądany, jeśli jakiekolwiek wywołania zostaną zamknięte lub usunięte, należy anulować subskrypcję (lub dodać logikę warunkową), aby zapobiec jej wykonaniu.

Rozważ kilka przypadków:

1) Formularz logowania. Podaj nazwę użytkownika i hasło, a następnie kliknij „Zaloguj się”. Co jeśli serwer działa wolno i zdecydujesz się nacisnąć Escape, aby zamknąć okno dialogowe? Prawdopodobnie będziesz zakładać, że nie jesteś zalogowany, ale jeśli żądanie HTTP zwróciło się po naciśnięciu klawisza Escape, nadal będziesz wykonywać dowolną logikę. Może to spowodować przekierowanie na stronę konta, ustawienie niepożądanego pliku cookie logowania lub zmiennej tokena. Prawdopodobnie nie tego oczekiwał użytkownik.

2) Formularz „wyślij e-mail”.

Jeśli subscribemoduł obsługi funkcji „sendEmail” wywoła animację „Twój e-mail został wysłany”, przenieś Cię na inną stronę lub spróbujesz uzyskać dostęp do wszystkiego, co zostało usunięte, możesz uzyskać wyjątki lub niepożądane zachowanie.

Uważaj również, aby nie zakładać, że unsubscribe()oznacza „anuluj”. Gdy wiadomość HTTP jest w locie unsubscribe(), NIE anuluje żądania HTTP, jeśli już dotarło do twojego serwera. To tylko anuluje odpowiedź, która do ciebie wróci. I e-mail prawdopodobnie zostanie wysłany.

Jeśli utworzysz subskrypcję, aby wysyłać wiadomości e-mail bezpośrednio w składniku interfejsu użytkownika, prawdopodobnie zechcesz zrezygnować z subskrypcji, ale jeśli wiadomość e-mail jest wysyłana przez usługę scentralizowaną bez interfejsu użytkownika, prawdopodobnie nie będzie to konieczne.

3) Element kątowy, który jest zniszczony / zamknięty. Wszelkie obserwowalne http, które nadal działają w tym czasie, zakończą się i uruchomią swoją logikę, chyba że anulujesz subskrypcję onDestroy(). To, czy konsekwencje będą trywialne, czy nie, zależy od tego, co robisz w module obsługi subskrypcji. Jeśli spróbujesz zaktualizować coś, co już nie istnieje, może pojawić się błąd.

Czasami możesz wykonać pewne działania, które chcesz, jeśli składnik zostanie usunięty, a niektóre nie. Na przykład może masz dźwięk „szumu” dla wysłanej wiadomości e-mail. Prawdopodobnie chciałbyś, aby to grało, nawet jeśli komponent był zamknięty, ale jeśli spróbujesz uruchomić animację na komponencie, zakończy się niepowodzeniem. W takim przypadku rozwiązaniem może być dodatkowa logika warunkowa w ramach subskrypcji - i NIE chcesz zrezygnować z subskrypcji obserwowalnego http.

Więc w odpowiedzi na aktualne pytanie, nie musisz tego robić, aby uniknąć wycieków pamięci. Ale musisz to zrobić (często), aby uniknąć niepożądanych efektów ubocznych uruchamianych przez kod, który może zgłaszać wyjątki lub uszkodzić stan aplikacji.

Wskazówka: SubscriptionZawiera closedwłaściwość logiczną, która może być przydatna w zaawansowanych przypadkach. W przypadku HTTP zostanie to ustawione po zakończeniu. W Angular może być użyteczne w niektórych sytuacjach, aby ustawić _isDestroyedwłaściwość, w ngDestroyktórej może być sprawdzony przez subscribeprogram obsługi.

Wskazówka 2: W przypadku obsługi wielu subskrypcji możesz utworzyć obiekt ad-hoc new Subscription()i add(...)wszelkie inne subskrypcje do niego - więc anulowanie subskrypcji głównej spowoduje anulowanie subskrypcji również wszystkich dodanych subskrypcji.


2
Zauważ też, że jeśli masz usługę, która zwraca surową możliwą do przeoczenia http, a następnie potokujesz ją przed subskrypcją, musisz jedynie anulować subskrypcję końcowego obserwowalnego, a nie bazowego obserwowalnego http. W rzeczywistości nie będziesz nawet mieć abonamentu na http bezpośrednio, więc nie możesz.
Simon_Weaver,

Mimo że anulowanie subskrypcji anuluje żądanie przeglądarki, serwer nadal reaguje na to żądanie. Czy istnieje sposób na zintensyfikowanie serwera, aby przerwał również operację? Wysyłam żądania HTTP w krótkich odstępach czasu, z których większość jest anulowana przez rezygnację z subskrypcji. Ale serwer nadal działa na żądaniach anulowanych przez klienta, co powoduje, że uzasadnione żądanie czeka.
bala,

@bala musiałbyś wymyślić własny mechanizm - który nie miałby nic wspólnego z RxJS. Na przykład możesz po prostu umieścić żądania w tabeli i uruchomić je po 5 sekundach w tle, a jeśli coś trzeba anulować, po prostu usuniesz lub ustawisz flagę na starszych wierszach, co uniemożliwi ich wykonanie. Zależy to całkowicie od Twojej aplikacji. Ponieważ jednak wspominasz, że serwer blokuje, może być skonfigurowany tak, aby zezwalał tylko na jedno żądanie na raz - ale to znowu będzie zależeć od tego, czego używasz.
Simon_Weaver

IpPorad 2 - jest PRO-TIP 🔥🔥🔥 dla każdego, kto chce zrezygnować z subskrypcji wielokrotnej, wywołując tylko jedną funkcję. Dodaj za pomocą metody .add () i niż .unsubscribe () w ngDestroy.
abhay tripathi

23

Wywołanie unsubscribemetody polega raczej na anulowaniu trwającego żądania HTTP, ponieważ metoda ta wywołuje tę abortna bazowym obiekcie XHR i usuwa detektory zdarzeń obciążenia i błędów:

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

To powiedziawszy, unsubscribeusuwa słuchaczy ... Może to być dobry pomysł, ale nie sądzę, że jest to konieczne do pojedynczej prośby ;-)

Mam nadzieję, że ci to pomoże, Thierry


: | to absurdalne: | szukałem sposobu na zatrzymanie ... ale zastanawiałem się, a jeśli to ja będę kodować, w pewnym scenariuszu: zrobiłbym to w ten sposób: y = x.subscribe((x)=>data = x); wtedy zmiana danych wejściowych użytkownika i x.subscribe((x)=>cachlist[n] = x); y.unsubscribe()hej, wykorzystałeś nasze zasoby serwerowe, wygrałem wyrzucę cię ..... w innym scenariuszu: po prostu zadzwoń y.stop()wyrzuć wszystko
deadManN

16

Rezygnacja z subskrypcji jest NIEZBĘDNA, jeśli chcesz deterministycznego zachowania na wszystkich prędkościach sieci.

Wyobraź sobie, że komponent A jest renderowany na karcie - Kliknij przycisk, aby wysłać żądanie „GET”. Odpowiedź trwa 200 ms. Tak więc możesz bezpiecznie zamknąć kartę w dowolnym momencie, wiedząc, że urządzenie będzie szybsze niż Ty, a odpowiedź HTTP zostanie przetworzona i zakończy się przed zamknięciem karty i zniszczeniem komponentu A.

Co powiesz na bardzo powolną sieć? Klikasz przycisk, żądanie „GET” trwa 10 sekund, aby otrzymać odpowiedź, ale po 5 sekundach czekania zdecydujesz się zamknąć kartę. Spowoduje to zniszczenie składnika A, który zostanie później wyrzucony. Poczekaj minutę! , nie anulowaliśmy subskrypcji - teraz 5 sekund później wraca odpowiedź i logika w zniszczonym komponencie zostanie wykonana. To wykonanie jest teraz rozważane out-of-contexti może spowodować wiele rzeczy, w tym bardzo niską wydajność.

Dlatego najlepszą praktyką jest używanie takeUntil()i anulowanie subskrypcji wywołań http, gdy składnik zostanie zniszczony.

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

Czy mogę używać BehaviorSubject()zamiast zadzwonić i właśnie complete()w ngOnDestroy()?
Saksham

Nadal będziesz musiał zrezygnować z subskrypcji ... dlaczego miałoby BehaviourSubjectbyć inaczej?
Ben Taliadoros

Nie ma potrzeby anulowania subskrypcji obserwowalności HttpClient, ponieważ są one obserwowalnościami skończonymi, tzn. Wypełniają się po wyemitowaniu wartości.
Yatharth Varshney

Zrezygnuj z subskrypcji, załóżmy, że istnieje przechwytywacz do wznoszenia błędów, ale opuściłeś już stronę, która spowodowała błąd. Zobaczysz komunikat o błędzie na innej stronie .... Pomyśl także o stronie sprawdzania kondycji, która wywołuje wszystkie mikrousług ... i tak dalej
Washington Guedes

12

Również z nowym modułem HttpClient pozostaje takie samo zachowanie Package / common / http / src / jsonp.ts


Powyższy kod wydaje się pochodzić z testu jednostkowego, co sugeruje, że za pomocą HttpClient musisz wywołać .complete () we własnym zakresie ( github.com/angular/angular/angular/blob/… )
CleverPatrick

Tak, masz rację, ale jeśli sprawdzisz ( github.com/angular/angular/blob/... ) zobaczysz to samo. Jeśli chodzi o główne pytanie, jeśli to konieczne, aby anulować subskrypcję? większość przypadków nie, ponieważ sam Angular zajmie się tym. Może to być przypadek, w którym może nastąpić długa reakcja, a ty przeszedłeś na inną trasę, a może zniszczyłeś komponent, w którym to przypadku możesz spróbować uzyskać dostęp do czegoś, co już nie istnieje, zgłaszając wyjątek.
Ricardo Martínez

8

Nie należy wypisywać się z obserwowalnych elementów, które kończą się automatycznie (np. HTTP, połączenia). Ale konieczne jest zrezygnowanie z nieskończonej ilości obserwowalnych obiektów, takich jak Observable.timer().


6

Po chwili testowania przeczytałem dokumentację i kod źródłowy 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/

Ten szczególny typ Obserwowalnych jest strumieniami o pojedynczej wartości: jeśli żądanie HTTP zakończy się powodzeniem, te obserwowalne wyemitują tylko jedną wartość, a następnie zakończą

A odpowiedź na cały problem „czy muszę się wypisać”?

To zależy. Połączenie HTTP Nieszczelności pamięci nie stanowią problemu. Problemy dotyczą logiki funkcji zwrotnych.

Na przykład: Routing lub Login.

Jeśli Twoje połączenie jest logowaniem, nie musisz „anulować subskrypcji”, ale musisz się upewnić, że jeśli użytkownik opuści stronę, odpowiednio zareagujesz na nieobecność użytkownika.


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

Od irytujących po niebezpieczne

Teraz wyobraź sobie, że sieć działa wolniej niż zwykle, połączenie trwa dłużej 5 sekund, a użytkownik opuszcza widok logowania i przechodzi do „widoku wsparcia”.

Składnik może nie być aktywny, ale subskrypcja. W przypadku odpowiedzi użytkownik zostanie nagle przekierowany (w zależności od implementacji handleResponse ()).

To nie jest dobre.

Wyobraź sobie również, że użytkownik opuszcza komputer, wierząc, że nie jest jeszcze zalogowany. Ale logika loguje użytkownika, teraz masz problem z bezpieczeństwem.

Co możesz zrobić BEZ rezygnacji z subskrypcji?

Uzależnij połączenie od aktualnego stanu widoku:

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

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

Użytkownik, .pipe(takeWhile(value => this.isActive))aby upewnić się, że odpowiedź jest obsługiwana tylko wtedy, gdy widok jest aktywny.


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

Ale skąd masz pewność, że subskrypcja nie powoduje wycieków pamięci?

Możesz się zalogować, jeśli zastosowano „teardownLogic”.

TeardownLogic subskrypcji zostanie wywołany, gdy subskrypcja będzie pusta lub anulowana.


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

Nie musisz rezygnować z subskrypcji. Powinieneś wiedzieć, czy w Twojej logice występują problemy, które mogą powodować problemy z subskrypcją. I zaopiekuj się nimi. W większości przypadków nie będzie to problemem, ale szczególnie przy krytycznych zadaniach, takich jak autoryzacja, powinieneś zadbać o nieoczekiwane zachowanie, zastępując je „anulowaniem subskrypcji” lub inną logiką, taką jak piping lub warunkowe funkcje zwrotne.

dlaczego nie zawsze zrezygnować z subskrypcji?

Wyobraź sobie, że składasz żądanie sprzedaży lub postu Serwer odbiera wiadomość w obu kierunkach, tylko odpowiedź trwa chwilę. Anulowanie subskrypcji nie spowoduje cofnięcia postu ani umieszczenia. Ale gdy anulujesz subskrypcję, nie będziesz mieć możliwości obsłużenia odpowiedzi lub poinformowania użytkownika, na przykład za pomocą okna dialogowego lub wiadomości / wiadomości itp.

Co powoduje, że Użytkownik uważa, że ​​żądanie umieszczenia / wysłania nie zostało wykonane.

Więc to zależy. To Twoja decyzja projektowa, jak poradzić sobie z takimi problemami.


4

Zdecydowanie powinieneś przeczytać ten artykuł. Pokazuje, dlaczego zawsze powinieneś wypisać się nawet z http .

Jeśli po utworzeniu żądania, ale przed otrzymaniem odpowiedzi z zaplecza, uznasz komponent za niepotrzebny i go zniszczysz, twoja subskrypcja zachowa odniesienie do komponentu, tworząc w ten sposób szansę na wycieki pamięci.

Aktualizacja

Powyższa afirmacja wydaje się być prawdą, ale w każdym razie, kiedy odpowiedź wróci, subskrypcja HTTP i tak zostanie zniszczona


1
Tak, a na karcie sieciowej przeglądarki zobaczysz czerwony „anuluj”, aby mieć pewność, że działa.
Simon_Weaver

-2

Obserwowalne RxJS są w zasadzie powiązane i działają odpowiednio, jeśli je subskrybujesz. Kiedy tworzymy obserwowalny ruch i wykonujemy go, obserwowalny automatycznie zostaje zamknięty i anulowany.

Działają w ten sam sposób, co obserwatorzy, ale w zupełnie innej kolejności. Lepszą praktyką jest anulowanie ich, gdy komponent się niszczy. Następnie możemy to zrobić np. this. $ manageSubscription.unsubscibe ()

Jeśli stworzyliśmy obserwowalną składnię, taką jak poniżej, taką jak

** zwróć nowy Obserwowalny ((obserwator) => {** // To sprawia, że ​​można go zaobserwować w stanie zimnym ** observer.complete () **}) **

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.