¿Es necesario darse de baja de los observables creados por los métodos Http?


211

¿Necesita darse de baja de las llamadas http de Angular 2 para evitar pérdidas de 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());
            ...


1
Ver stackoverflow.com/questions/34461842/… (y los comentarios también)
Eric Martinez

Respuestas:


253

Entonces la respuesta es no, no lo haces. Ng2lo limpiará solo.

La fuente del servicio Http, de la fuente de back-end Http XHR de Angular:

ingrese la descripción de la imagen aquí

Observe cómo se ejecuta complete()después de obtener el resultado. Esto significa que en realidad se da de baja al finalizar. Por lo tanto, no necesita hacerlo usted mismo.

Aquí hay una prueba para validar:

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

Como se esperaba, puede ver que siempre se da de baja automáticamente después de obtener el resultado y terminar con los operadores observables. Esto sucede en un tiempo de espera (# 3) para que podamos verificar el estado del observable cuando todo esté hecho y completado.

Y el resultado

ingrese la descripción de la imagen aquí

Por lo tanto, no existiría ninguna fuga, ya que se da de Ng2baja automáticamente

Es bueno mencionar: esto Observablese clasifica como finite, al contrario de lo infinite Observableque se puede emitir un flujo infinito de datos como DOM clickoyente, por ejemplo.

GRACIAS, @rubyboy por su ayuda en esto.


17
¿Qué sucede en los casos en que el usuario abandona la página antes de que llegue la respuesta, o si la respuesta nunca llega antes de que el usuario abandone la vista? ¿Eso no causaría una fuga?
Thibs

1
Me gustaría saber lo anterior también.
1984

44
@ 1984 La respuesta es SIEMPRE INSCRIBIRSE. Esta respuesta es completamente incorrecta por exactamente la razón mencionada en este comentario. El usuario que navega es un enlace de memoria. Además, si el código en esa suscripción se ejecuta después de que el usuario se aleja, podría causar errores / tener efectos secundarios inesperados. Tiendo a usar takeWhile (() => this.componentActive) en todos los observables y solo configuro this.componentActive = false en ngOnDestroy para limpiar todos los observables en un componente.
Lenny

44
El ejemplo que se muestra aquí cubre una API privada angular profunda. con cualquier otra API privada, puede cambiar con una actualización sin previo aviso. La regla general es: si no está documentado oficialmente, no haga suposiciones. Por ejemplo, AsynPipe tiene una documentación clara de que se suscribe / cancela automáticamente. Los documentos de HttpClient no mencionan nada al respecto.
YoussefTaghlabi

1
@YoussefTaghlabi, para su información, la documentación angular oficial ( angular.io/guide/http ) menciona que: "AsyncPipe se suscribe (y cancela) automáticamente". Entonces, de hecho está oficialmente documentado.
Hudgi

96

¿De qué están hablando?

OK, hay dos razones para darse de baja de cualquier observable. ¡Nadie parece estar hablando mucho sobre la muy importante segunda razón!

1) Limpiar recursos. Como otros han dicho, este es un problema insignificante para los observables HTTP. Simplemente se limpiará solo.

2) Evitar que subscribese ejecute el controlador.

(Para HTTP, esto también cancelará la solicitud en el navegador, por lo que no perderá tiempo leyendo la respuesta. Pero eso es en realidad un aparte de mi punto principal a continuación).

La relevancia del número 2 dependerá de lo que haga su controlador de suscripción:

Si su subscribe()función de controlador tiene algún tipo de efecto secundario no deseado si se cierra o se elimina cualquier llamada, entonces debe cancelar la suscripción (o agregar lógica condicional) para evitar que se ejecute.

Considere algunos casos:

1) Un formulario de inicio de sesión. Ingresas nombre de usuario y contraseña y haces clic en 'Iniciar sesión'. ¿Qué sucede si el servidor es lento y decide presionar Escape para cerrar el cuadro de diálogo? Probablemente asumirás que no has iniciado sesión, pero si la solicitud http regresó después de presionar escape, entonces aún ejecutarás cualquier lógica que tengas allí. Esto puede dar lugar a una redirección a una página de cuenta, una cookie de inicio de sesión no deseada o una variable de token configurada. Probablemente esto no sea lo que esperaba su usuario.

2) Un formulario de 'enviar correo electrónico'.

Si el subscribecontrolador para 'sendEmail' hace algo como activar una animación 'Se envía su correo electrónico', lo transfiere a una página diferente o intenta acceder a cualquier cosa que haya sido eliminada, puede obtener excepciones o comportamientos no deseados.

También tenga cuidado de no asumir que unsubscribe()significa 'cancelar'. Una vez que el mensaje HTTP está en vuelo unsubscribe()NO cancelará la solicitud HTTP si ya ha llegado a su servidor. Solo cancelará la respuesta que vuelve a usted. Y el correo electrónico probablemente se enviará.

Si crea la suscripción para enviar el correo electrónico directamente dentro de un componente de la interfaz de usuario, es probable que desee darse de baja al deshacerse, pero si el correo electrónico lo envía un servicio centralizado que no es la interfaz de usuario, entonces probablemente no necesite hacerlo.

3) Un componente angular que se destruye / cierra. Todos los observables http que aún se estén ejecutando en ese momento se completarán y ejecutarán su lógica a menos que cancele la suscripciónonDestroy() . Si las consecuencias son triviales o no dependerá de lo que haga en el controlador de suscripción. Si intenta actualizar algo que ya no existe, puede recibir un error.

A veces puede tener algunas acciones que desearía si el componente se elimina, y otras no. Por ejemplo, tal vez tenga un sonido 'swoosh' para un correo electrónico enviado. Probablemente desee que esto se reproduzca incluso si el componente estaba cerrado, pero si intenta ejecutar una animación en el componente, fallará. En ese caso, una solución condicional adicional dentro de la suscripción sería la solución, y NO querrás cancelar la suscripción del http observable.

Entonces, en respuesta a la pregunta real, no, no necesita hacerlo para evitar pérdidas de memoria. Pero debe hacerlo (a menudo) para evitar que se activen efectos secundarios no deseados al ejecutar código que pueda generar excepciones o corromper el estado de su aplicación.

Consejo: Subscriptioncontiene una closedpropiedad booleana que puede ser útil en casos avanzados. Para HTTP, esto se establecerá cuando se complete. En Angular, puede ser útil en algunas situaciones establecer una _isDestroyedpropiedad en la ngDestroyque su controlador pueda verificarla subscribe.

Consejo 2: Si maneja múltiples suscripciones, puede crear un new Subscription()objeto ad-hoc y add(...)cualquier otra suscripción a él, por lo que cuando cancele la suscripción de la principal, también cancelará la suscripción de todas las suscripciones agregadas.


2
También tenga en cuenta que si tiene un servicio que devuelve el http sobrecargable en bruto y luego lo canaliza antes de suscribirse, solo necesita darse de baja del observable final, no del observable http subyacente. De hecho, ni siquiera tendrá una Suscripción a http directamente, por lo que no puede.
Simon_Weaver

Aunque la cancelación de la suscripción cancela la solicitud del navegador, el servidor aún actúa sobre la solicitud. ¿Hay alguna manera de intimidar al servidor para que también cancele la operación? Estoy enviando solicitudes http en rápida sucesión, la mayoría de las cuales se cancelan al cancelar la suscripción. Pero, el servidor aún opera con las solicitudes canceladas por el cliente, lo que hace que la solicitud legítima espere.
bala

@bala, tendría que crear su propio mecanismo para esto, que no tendría nada que ver con RxJS. Por ejemplo, puede simplemente poner las solicitudes en una tabla y ejecutarlas después de 5 segundos en segundo plano, luego, si algo necesita ser cancelado, simplemente eliminaría o establecería una bandera en las filas más antiguas que impediría su ejecución. Dependería completamente de cuál sea su aplicación. Sin embargo, dado que usted menciona que el servidor está bloqueando, puede configurarse para permitir solo una solicitud a la vez, pero eso dependerá nuevamente de lo que esté utilizando.
Simon_Weaver

🔥🔥🔥Tip 2 - es un PRO-TIP 🔥🔥🔥 para cualquier persona que quiera darse de baja de una suscripción múltiple llamando a una sola función. Agregue usando el método .add () y luego .unsubscribe () en ngDestroy.
abhay tripathi

23

Llamar al unsubscribemétodo es más bien cancelar una solicitud HTTP en curso ya que este método llama al abortdel objeto XHR subyacente y elimina los escuchas en los eventos de carga y error:

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

Dicho esto, unsubscribeelimina a los oyentes ... Por lo tanto, podría ser una buena idea, pero no creo que sea necesario para una sola solicitud ;-)

Espero que te ayude, Thierry


: | eso es absurdo: | Estaba buscando una manera de detener ... pero me preguntaba, y si fuera yo la codificación, en algún escenario: lo haría así: y = x.subscribe((x)=>data = x); luego un cambio de usuario en las entradas y x.subscribe((x)=>cachlist[n] = x); y.unsubscribe()oye, usaste los recursos de nuestro servidor, gané 'tirar a la basura ..... y en otro escenario: simplemente llame a y.stop()tirar todo a la basura
deadManN

16

Darse de baja es imprescindible si desea un comportamiento determinista en todas las velocidades de red.

Imagina que el componente A se representa en una pestaña: hace clic en un botón para enviar una solicitud 'OBTENER'. La respuesta tarda 200 ms en volver. Por lo tanto, puede cerrar la pestaña en cualquier momento sabiendo que la máquina será más rápida que usted y la respuesta http se procesará y se completará antes de que se cierre la pestaña y se destruya el componente A.

¿Qué tal en una red muy lenta? Al hacer clic en un botón, la solicitud 'OBTENER' tarda 10 segundos en recibir su respuesta, pero 5 segundos después de esperar, decide cerrar la pestaña. Eso destruirá el componente A para ser recolectado en un momento posterior. ¡Espera un minuto! , no cancelamos la suscripción; ahora, 5 segundos después, vuelve una respuesta y se ejecutará la lógica en el componente destruido. Esa ejecución ahora se considera out-of-contexty puede dar lugar a muchas cosas, incluido un rendimiento muy bajo.

Por lo tanto, la mejor práctica es usar takeUntil()y cancelar la suscripción de llamadas http cuando se destruye el componente.

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

¿Se puede usar BehaviorSubject()en su lugar y llamar simplemente complete()en ngOnDestroy()?
Saksham

Aún necesitaría darse de baja ... ¿por qué sería BehaviourSubjectdiferente?
Ben Taliadoros

No es necesario darse de baja de los observables HttpClient ya que son observables finitos, es decir, se completan después de emitir un valor.
Yatharth Varshney

Debería darse de baja de todos modos, supongamos que hay un interceptor para brindar los errores, pero ya ha abandonado la página que activó el error ... verá un mensaje de error en otra página ... También piense en una página de verificación de estado que llama a todos los microservicios ... y así sucesivamente
Washington Guedes

12

También con el nuevo módulo HttpClient, sigue siendo el mismo comportamiento paquetes / común / http / src / jsonp.ts


El código anterior parece ser de una prueba unitaria, lo que implica que con HttpClient, DEBE llamar a .complete () por su cuenta ( github.com/angular/angular/blob/… )
CleverPatrick

Sí, tienes razón, pero si inspeccionas ( github.com/angular/angular/blob/… ) verás lo mismo. En cuanto a la pregunta principal, si es necesario darse de baja explícitamente? la mayoría de los casos no, ya que será manejado por Angular. Podría ser un caso en el que podría tener lugar una respuesta prolongada y usted se mudó a otra ruta o tal vez destruyó un componente, en cuyo caso tiene la posibilidad de intentar acceder a algo que ya no existe, lo que genera una excepción.
Ricardo Martínez

8

No debe darse de baja de los observables que se completa automáticamente (por ejemplo, Http, llamadas). Pero es necesario darse de baja de infinitos observables como Observable.timer().


6

Después de un tiempo de prueba, leer la documentación y el código fuente del 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/

Universidad Angular: https://blog.angular-university.io/angular-http/

Este tipo particular de Observables son flujos de un solo valor: si la solicitud HTTP es exitosa, estos observables emitirán solo un valor y luego se completarán

¿Y la respuesta a toda la cuestión de "NECESITO" darme de baja?

Depende. Las llamadas de memoria HTTP no son un problema. Los problemas son la lógica en sus funciones de devolución de llamada.

Por ejemplo: enrutamiento o inicio de sesión.

Si su llamada es una llamada de inicio de sesión, no tiene que "darse de baja", pero debe asegurarse de que si el Usuario abandona la página, maneje la respuesta correctamente en ausencia del usuario.


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

De molesto a peligroso

Ahora imagine que la red es más lenta de lo habitual, la llamada tarda más de 5 segundos y el usuario abandona la vista de inicio de sesión y se dirige a una "vista de soporte".

El componente puede no estar activo pero la suscripción. En caso de una respuesta, el usuario se redirigirá repentinamente (dependiendo de la implementación de handleResponse ()).

Esto no es bueno

También imagine que el usuario deja la PC, creyendo que aún no ha iniciado sesión. Pero su lógica inicia sesión en el usuario, ahora tiene un problema de seguridad.

¿Qué puedes hacer SIN darte de baja?

Haga que su llamada dependa del estado actual de la vista:

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

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

Usuario .pipe(takeWhile(value => this.isActive))para asegurarse de que la respuesta solo se maneja cuando la vista está activa.


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

Pero, ¿cómo puede estar seguro de que la suscripción no está causando pérdidas de memoria?

Puede iniciar sesión si se aplica "teardownLogic".

Se llamará al teardownLogic de una suscripción cuando la suscripción esté vacía o cancelada.


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

No tiene que darse de baja. Debe saber si hay problemas en su lógica, que podrían causar problemas en su suscripción. Y cuídalos. En la mayoría de los casos, no será un problema, pero especialmente en tareas críticas como la autorización, debe tener cuidado con el comportamiento inesperado, ya sea con "darse de baja" u otra lógica como la canalización o las funciones de devolución de llamada condicional.

¿Por qué no siempre darse de baja?

Imagina que haces una solicitud de venta o publicación. El servidor recibe el mensaje de cualquier manera, solo la respuesta lleva un tiempo. Darse de baja, no deshacerá la publicación ni la pondrá. Pero cuando se da de baja, no tendrá la oportunidad de manejar la respuesta o informar al Usuario, por ejemplo, a través de Diálogo o un Brindis / Mensaje, etc.

Lo que hace que el usuario crea que la solicitud de poner / publicar no se realizó.

Entonces eso depende. Es su decisión de diseño, cómo lidiar con tales problemas.


4

Definitivamente deberías leer este artículo. Le muestra por qué siempre debe darse de baja incluso de http .

Si después de crear la solicitud pero antes de recibir una respuesta del back-end considera que el componente es innecesario y lo destruye, su suscripción mantendrá la referencia al componente, creando así la posibilidad de causar pérdidas de memoria.

Actualizar

La afirmación anterior parece ser cierta, pero de todos modos, cuando la respuesta vuelve, la suscripción http se destruye de todos modos


1
Sí, y en realidad verá una 'cancelación' roja en la pestaña de red de su navegador para que pueda estar seguro de que funciona.
Simon_Weaver 01 de

-2

Los observables RxJS están básicamente asociados y funcionan en consecuencia, lo suscribe. Cuando creamos el observable y el movimiento que lo completamos, el observable se cierra y cancela automáticamente.

Trabajan de la misma manera que los observadores pero con un orden bastante diferente. La mejor práctica para cancelar la suscripción cuando el componente se está destruyendo. Posteriormente podríamos hacerlo, por ejemplo. this. $ manageSubscription.unsubscibe ()

Si hemos creado el observable como la sintaxis mencionada a continuación, como

** return new Observable ((observador) => {** // Hace observable en estado frío ** observer.complete () **}) **

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.