Est-il nécessaire de se désinscrire des observables créés par les méthodes Http?


211

Devez-vous vous désabonner des appels http Angular 2 pour éviter les fuites de mémoire?

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


1
Voir stackoverflow.com/questions/34461842/… (et les commentaires également)
Eric Martinez

Réponses:


253

Donc, la réponse est non, vous ne le faites pas. Ng2va le nettoyer lui-même.

La source du service Http, à partir de la source backend Http XHR d'Angular:

entrez la description de l'image ici

Remarquez comment il s'exécute complete()après avoir obtenu le résultat. Cela signifie qu'il se désabonne à la fin. Vous n'avez donc pas besoin de le faire vous-même.

Voici un test à valider:

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

Comme prévu, vous pouvez voir qu'il est toujours automatiquement désabonné après avoir obtenu le résultat et fini avec les opérateurs observables. Cela se produit sur un délai d'attente (# 3) afin que nous puissions vérifier l'état de l'observable une fois terminé et terminé.

Et le résultat

entrez la description de l'image ici

Ainsi, aucune fuite n'existerait lors de Ng2la désinscription automatique!

Agréable à mentionner: ceci Observableest catégorisé car finite, contrairement à ce infinite Observablequi est un flux infini de données peut être émis comme l' clickécouteur DOM par exemple.

MERCI, @rubyboy pour votre aide à ce sujet.


17
que se passe-t-il dans les cas où l'utilisateur quitte la page avant que la réponse n'arrive, ou si la réponse ne vient jamais avant que l'utilisateur ne quitte la vue? Cela ne provoquerait-il pas une fuite?
Thibs

1
J'aimerais également savoir ce qui précède.
1984

4
@ 1984 La réponse est TOUJOURS DÉSABONNER. Cette réponse est tout à fait erronée pour exactement la raison évoquée dans ce commentaire. L'utilisateur qui s'éloigne est un lien mémoire. De plus, si le code dans cet abonnement s'exécute après que l'utilisateur s'est éloigné, cela pourrait provoquer des erreurs / avoir des effets secondaires inattendus. J'ai tendance à utiliser takeWhile (() => this.componentActive) sur tous les observables et à définir this.componentActive = false dans ngOnDestroy pour nettoyer tous les observables d'un composant.
Lenny

4
L'exemple affiché ici couvre une API privée angulaire profonde. avec toute autre API privée, elle peut changer avec une mise à niveau sans préavis. La règle générale est la suivante: si ce n'est pas officiellement documenté, ne faites aucune hypothèse. Par exemple, l'AsynPipe a une documentation claire qu'il s'abonne / se désabonne automatiquement pour vous. Les documents HttpClient ne mentionnent rien à ce sujet.
YoussefTaghlabi

1
@YoussefTaghlabi, FYI, la documentation angulaire officielle ( angular.io/guide/http ) mentionne que: "L'AsyncPipe s'abonne (et se désabonne) pour vous automatiquement." Donc, c'est en effet officiellement documenté.
Hudgi

96

De quoi parlez-vous!!!

OK donc il y a deux raisons de se désinscrire de tout observable. Personne ne semble beaucoup parler de la deuxième raison très importante!

1) Nettoyez les ressources. Comme d'autres l'ont dit, c'est un problème négligeable pour les observables HTTP. Il va juste se nettoyer.

2) Empêchez l' subscribeexécution du gestionnaire.

(Pour HTTP, cela annulera également la demande dans le navigateur - donc cela ne perdra pas de temps à lire la réponse. Mais c'est en fait un aparté à mon point principal ci-dessous.)

La pertinence du numéro 2 dépendra de ce que fait votre gestionnaire d'abonnement:

Si votre subscribe()fonction de gestionnaire a tout type d'effet secondaire indésirable si tout ce qui l'appelle est fermé ou supprimé, vous devez vous désinscrire (ou ajouter une logique conditionnelle) pour empêcher son exécution.

Prenons quelques cas:

1) Un formulaire de connexion. Vous entrez le nom d'utilisateur et le mot de passe et cliquez sur «Connexion». Que faire si le serveur est lent et que vous décidez d'appuyer sur Échap pour fermer la boîte de dialogue? Vous supposerez probablement que vous n'étiez pas connecté, mais si la demande http est retournée après avoir appuyé sur escape, alors vous exécuterez toujours la logique que vous avez là-bas. Cela peut entraîner une redirection vers une page de compte, un cookie de connexion indésirable ou une variable de jeton en cours de définition. Ce n'est probablement pas ce que votre utilisateur attendait.

2) Un formulaire «envoyer un e-mail».

Si le subscribegestionnaire pour «sendEmail» fait quelque chose comme déclencher une animation «Votre e-mail est envoyé», vous transférer vers une autre page ou essayer d'accéder à tout ce qui a été supprimé, vous pouvez obtenir des exceptions ou un comportement indésirable.

Faites également attention à ne pas supposer unsubscribe()«annuler». Une fois que le message HTTP est en vol, unsubscribe()il n'annulera PAS la demande HTTP si elle a déjà atteint votre serveur. Cela n'annulera que la réponse qui vous reviendra. Et l'e-mail sera probablement envoyé.

Si vous créez l'abonnement pour envoyer l'e-mail directement à l'intérieur d'un composant d'interface utilisateur, vous souhaiterez probablement vous désabonner lors de la suppression, mais si l'e-mail est envoyé par un service centralisé non-interface utilisateur, vous n'en aurez probablement pas besoin.

3) Un composant angulaire qui est détruit / fermé. Tous les observables http toujours en cours d'exécution à ce moment-là se termineront et exécuteront leur logique, sauf si vous vous désabonnez onDestroy(). Que les conséquences soient triviales ou non dépendra de ce que vous faites dans le gestionnaire d'abonnement. Si vous essayez de mettre à jour quelque chose qui n'existe plus, vous pouvez obtenir une erreur.

Parfois, vous pouvez avoir certaines actions que vous souhaiteriez si le composant est supprimé, et d'autres non. Par exemple, vous avez peut-être un son «swoosh» pour un e-mail envoyé. Vous voudriez probablement que cela fonctionne même si le composant était fermé, mais si vous essayez d'exécuter une animation sur le composant, cela échouerait. Dans ce cas, une logique conditionnelle supplémentaire à l'intérieur de subscribe serait la solution - et vous ne voudriez PAS désinscrire le http observable.

Donc, en réponse à la question réelle, non, vous n'avez pas besoin de le faire pour éviter les fuites de mémoire. Mais vous devez le faire (souvent) pour éviter que des effets secondaires indésirables ne soient déclenchés par l'exécution de code qui peut lever des exceptions ou corrompre l'état de votre application.

Conseil: Le Subscriptioncontient une closedpropriété booléenne qui peut être utile dans les cas avancés. Pour HTTP, cela sera défini à la fin. Dans Angular, il peut être utile dans certaines situations de définir une _isDestroyedpropriété ngDestroyqui peut être vérifiée par votre subscribegestionnaire.

Astuce 2: Si vous gérez plusieurs abonnements, vous pouvez créer un new Subscription()objet ad-hoc et add(...)tout autre abonnement - donc lorsque vous vous désabonnez de celui-ci, il désabonnera également tous les abonnements ajoutés.


2
Notez également que si vous disposez d'un service qui retourne le http brut et que vous le dirigez avant de vous abonner, il vous suffit de vous désabonner de l'observable final, et non de l'observable http sous-jacent. En fait, vous n'aurez même pas d'abonnement à http directement, vous ne pouvez donc pas.
Simon_Weaver

Même si la désinscription annule la demande du navigateur, le serveur agit toujours sur la demande. Existe-t-il un moyen d'intimer le serveur pour annuler également l'opération? J'envoie rapidement des demandes http, dont la plupart sont annulées par désinscription. Mais, le serveur fonctionne toujours sur les demandes annulées par le client, ce qui fait attendre la demande légitime.
bala

@bala, vous devriez trouver votre propre mécanisme pour cela - ce qui n'aurait rien à voir avec RxJS. Par exemple, vous pouvez simplement placer les demandes dans une table et les exécuter après 5 secondes en arrière-plan, puis si quelque chose devait être annulé, vous supprimeriez ou définiriez un indicateur sur les anciennes lignes qui les empêcherait de s'exécuter. Dépendrait entièrement de votre application. Cependant, puisque vous mentionnez que le serveur bloque, il peut être configuré pour n'autoriser qu'une seule demande à la fois - mais cela dépendra à nouveau de ce que vous utilisiez.
Simon_Weaver

🔥🔥🔥 Astuce 2 - est un PRO-TIP, 🔥🔥🔥 pour toute personne qui souhaite se désabonner de plusieurs abonnements en appelant une seule fonction. Ajoutez en utilisant la méthode .add () et que .unsubscribe () dans ngDestroy.
abhay tripathi

23

L'appel de la unsubscribeméthode revient plutôt à annuler une requête HTTP en cours, car cette méthode appelle abortcelle sur l'objet XHR sous-jacent et supprime les écouteurs sur les événements de chargement et d'erreur:

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

Cela dit, unsubscribesupprime les auditeurs ... Donc ça pourrait être une bonne idée mais je ne pense pas que ce soit nécessaire pour une seule demande ;-)

J'espère que ça vous aide, Thierry


: | c'est absurde: | je cherchais un moyen de m'arrêter ... mais je me demandais, et si c'était moi le codage, dans certains cas: je le ferais comme ça: y = x.subscribe((x)=>data = x); alors un utilisateur change d'entrée et x.subscribe((x)=>cachlist[n] = x); y.unsubscribe()bon vous avez utilisé nos ressources serveur, j'ai gagné ne vous jetez pas ..... et dans un autre scénario: appelez simplement y.stop()tout jeter
deadManN

16

La désinscription est un MUST si vous voulez un déterministe comportement à toutes les vitesses du réseau.

Imaginer que le composant A soit rendu dans un onglet - Vous cliquez sur un bouton pour envoyer une demande «GET». Il faut 200 ms pour que la réponse revienne. Ainsi, vous êtes sûr de fermer l'onglet à tout moment en sachant que la machine sera plus rapide que vous et la réponse http est traitée et terminée avant que l'onglet ne soit fermé et que le composant A soit détruit.

Que diriez-vous d'un réseau très lent? Vous cliquez sur un bouton, la demande «GET» prend 10 secondes pour recevoir sa réponse, mais après 5 secondes d'attente, vous décidez de fermer l'onglet. Cela détruira le composant A devant être récupéré ultérieurement. Attends une minute! , nous ne nous sommes pas désabonnés - maintenant 5 secondes plus tard, une réponse revient et la logique du composant détruit sera exécutée. Cette exécution est maintenant envisagée out-of-contextet peut entraîner de nombreuses choses, notamment de très faibles performances.

Ainsi, la meilleure pratique consiste à utiliser takeUntil()et à vous désabonner des appels http lorsque le composant est détruit.

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

Est - il correct d'utiliser à la BehaviorSubject()place et il suffit d' appeler complete()en ngOnDestroy()?
Saksham

Vous auriez encore besoin de vous désinscrire ... pourquoi serait- BehaviourSubjectil différent?
Ben Taliadoros

Il n'est pas nécessaire de se désinscrire des observables HttpClient car ce sont des observables finis, c'est-à-dire qu'ils se terminent après avoir émis une valeur.
Yatharth Varshney le

Vous devez vous désinscrire de toute façon, supposons qu'il y ait un intercepteur pour porter un toast aux erreurs, mais vous avez déjà quitté la page qui a déclenché l'erreur .. vous verrez un message d'erreur dans une autre page .... Pensez également à une page de bilan de santé qui appelle tous les microservices ... et ainsi de suite
Washington Guedes

12

Toujours avec le nouveau module HttpClient, reste le même comportement packages / common / http / src / jsonp.ts


Le code ci-dessus semble provenir d'un test unitaire, ce qui implique qu'avec HttpClient, vous devez appeler vous-même .complete () ( github.com/angular/angular/blob/… )
CleverPatrick

Oui, vous avez raison, mais si vous inspectez ( github.com/angular/angular/blob/… ), vous verrez la même chose. Concernant la question principale, si nécessaire pour vous désinscrire explicitement? la plupart des cas non, car va être géré par Angular lui-même. Cela pourrait être un cas dans lequel une longue réponse pourrait avoir lieu et que vous avez déplacé vers une autre route ou peut-être détruit un composant, auquel cas vous avez la possibilité d'essayer d'accéder à quelque chose qui n'existe plus en levant une exception.
Ricardo Martínez

8

Vous ne devez pas vous désabonner des observables qui se terminent automatiquement (par exemple Http, appels). Mais il est nécessaire de se désabonner des observables infinis comme Observable.timer().


6

Après un certain temps de test, lecture de la documentation et du code source du 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é angulaire: https://blog.angular-university.io/angular-http/

Ce type particulier d'Observables sont des flux à valeur unique: si la requête HTTP aboutit, ces observables n'émettront qu'une seule valeur puis se termineront

Et la réponse à toute la question de "ai-je besoin" de me désinscrire?

Ça dépend. Les fuites de mémoire des appels HTTP ne sont pas un problème. Les problèmes sont la logique de vos fonctions de rappel.

Par exemple: routage ou connexion.

Si votre appel est un appel de connexion, vous n'avez pas à vous "désinscrire" mais vous devez vous assurer que si l'utilisateur quitte la page, vous gérez la réponse correctement en l'absence de l'utilisateur.


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

D'ennuyeux à dangereux

Imaginez maintenant, le réseau est plus lent que d'habitude, l'appel prend plus de 5 secondes et l'utilisateur quitte la vue de connexion et passe à une "vue de support".

Le composant n'est peut-être pas actif mais l'abonnement. En cas de réponse, l'utilisateur sera soudainement redirigé (selon votre implémentation de handleResponse ()).

Ce n'est pas bon.

Imaginez également que l'utilisateur quitte le PC, croyant qu'il n'est pas encore connecté. Mais votre logique connecte l'utilisateur, vous avez maintenant un problème de sécurité.

Que pouvez-vous faire SANS vous désinscrire?

Rendre votre appel dépendant de l'état actuel de la vue:

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

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

L'utilisateur .pipe(takeWhile(value => this.isActive))doit s'assurer que la réponse n'est gérée que lorsque la vue est active.


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

Mais comment être sûr que l'abonnement ne cause pas de fuites de mémoire?

Vous pouvez vous connecter si le "teardownLogic" est appliqué.

La déchirureLogic d'un abonnement sera appelée lorsque l'abonnement est vide ou non souscrit.


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

Vous n'êtes pas obligé de vous désinscrire. Vous devez savoir s'il y a des problèmes dans votre logique, qui pourraient causer des problèmes dans votre abonnement. Et prends soin d'eux. Dans la plupart des cas, ce ne sera pas un problème, mais surtout pour les tâches critiques telles que l'autorisation, vous devez faire attention aux comportements inattendus, que ce soit avec "unsubscribe" ou une autre logique comme la tuyauterie ou les fonctions de rappel conditionnel.

pourquoi ne pas simplement toujours vous désinscrire?

Imaginez que vous fassiez une demande de vente ou de publication. Le serveur reçoit le message de toute façon, la réponse prend un certain temps. Se désinscrire, n'annulera pas le message ni ne le mettra. Mais lorsque vous vous désabonnez, vous n'aurez pas la possibilité de gérer la réponse ou d'informer l'utilisateur, par exemple via une boîte de dialogue ou un toast / message, etc.

Ce qui fait croire à l'utilisateur que la demande put / post n'a pas été effectuée.

Cela dépend donc. C'est votre décision de conception, comment traiter ces problèmes.


4

Vous devriez certainement lire cet article. Il vous montre pourquoi vous devez toujours vous désinscrire, même de http .

Si, après avoir créé la demande mais avant de recevoir une réponse du serveur principal, vous estimez que le composant est inutile et le détruisez, votre abonnement conservera la référence au composant, créant ainsi une chance de provoquer des fuites de mémoire.

Mettre à jour

L'affirmation ci-dessus semble être vraie, mais de toute façon, quand la réponse revient, l'abonnement http est quand même détruit


1
Oui et vous verrez en fait une «annulation» rouge dans l'onglet réseau de votre navigateur afin que vous puissiez être sûr que cela fonctionne.
Simon_Weaver

-2

Les observables RxJS sont fondamentalement associés et fonctionnent en conséquence, vous vous y abonnez. Lorsque nous créons l'observable et le mouvement que nous le complétons, l'observable est automatiquement fermé et désabonné.

Ils travaillent de la même manière que les observateurs mais dans un ordre assez différent. La meilleure pratique pour les désinscrire lorsque le composant est en cours de destruction. Nous pourrions par la suite le faire par exemple. this. $ manageSubscription.unsubscibe ()

Si nous avons créé la syntaxe observable comme ci-dessous, telle que

** return new Observable ((observer) => {** // Il rend observable à froid ** observer.complete () **}) **

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.