Skip to content

Ciclo de Vida de Observable ​

Esta sección proporciona una descripción paso a paso del ciclo de vida de Observable en RxJS, desde la creación, suscripción, publicación de datos, notificación de finalización y error, cancelación de suscripción y gestión de recursos. Entender este ciclo de vida es la base para el uso efectivo de RxJS.

Descripción General del Ciclo de Vida de Observable ​

El ciclo de vida de Observable se refiere a la secuencia de cómo se crea un Observable, cuándo publica datos, cómo se termina o notifica de errores, y finalmente cuándo se liberan recursos. Al entender esto, es posible entender correctamente el comportamiento de RxJS, cancelar suscripción en el momento previsto, controlar efectos secundarios y manejar errores apropiadamente.

🔄 Diagrama del Ciclo de Vida de Observable (Transiciones de Estado) ​

Estructura del Ciclo de Vida de Observable ​

El ciclo de vida de Observable consiste en las siguientes fases:

OrdenFaseContenido
1CreaciónCrear una instancia de Observable
2SuscripciónIniciar suscripción con el método subscribe()
3Ejecución
  • next(): Publicar datos
  • error(): Notificación de error
  • complete(): Notificación de finalización
  • 4DisposiciónCancelación de suscripción con el método unsubscribe()

    Observable es "perezoso", lo que significa que no se emite ningún flujo hasta que se llame a subscribe(). Además, el flujo termina cuando se llama a complete() o error(), y cualquier llamada posterior a next() se ignora.

    CAUTION

    No se realiza ningún "procesamiento" de ningún tipo directamente por subscribe(). La expresión "No se ejecutará hasta que llame a subscribe()" se usa a menudo, pero para ser precisos, "la lógica interna de Observable (proceso de notificación)" se activa por primera vez mediante subscribe().

    Los operadores como pipe() solo construyen la cadena de evaluación; la ejecución es iniciada por los callbacks de Observer.

    Ejemplo de Ciclo de Vida de Observable ​

    ts
    import { Observable } from 'rxjs';
    
    // 1. Creación de Observable
    const observable$ = new Observable<number>(subscriber => {
      console.log('Ejecución de Observable iniciada');
    
      // 3. Ejecución: Publicación de datos
      subscriber.next(1);
      subscriber.next(2);
    
      // Configuración de temporizador
      const timerId = setTimeout(() => {
        subscriber.next(3);
        subscriber.complete(); // 3. Ejecución: Notificación de finalización
        console.log('Observable completado');
      }, 1000);
    
      // Devolver función de limpieza (llamada al cancelar suscripción)
      return () => {
        console.log('Limpieza ejecutada');
        clearTimeout(timerId);
      };
    });
    
    // 2. Suscripción
    const subscription = observable$.subscribe({
      next: value => console.log('Siguiente valor:', value),
      error: err => console.error('Error:', err),
      complete: () => console.log('Notificación de finalización recibida')
    });
    
    // 4. Cancelación de suscripción (manual o al completarse)
    setTimeout(() => {
      console.log('Cancelación manual de suscripción');
      subscription.unsubscribe();
    }, 500); // Cancelar suscripción a 500ms (antes de la notificación de finalización)
    
    // Salida:
    // Ejecución de Observable iniciada
    // Siguiente valor: 1
    // Siguiente valor: 2
    // Cancelación manual de suscripción
    // Limpieza ejecutada

    Observer ​

    Observer es un objeto que actúa como receptor de notificaciones de Observable. Al suscribirse a un Observable, pasa un Observer para procesar los datos emitidos, errores y notificaciones de finalización.

    Tres Funciones de Callback de Observer ​

    Observer es una interfaz para recibir notificaciones de Observable. Tiene tres funciones de callback:

    • next: Publicación de datos
    • error: Notificación de error
    • complete: Notificación de finalización

    Ejemplo ​

    ts
    import { Observer, of } from 'rxjs';
    
    // Objeto Observer completo
    const observer: Observer<number> = {
      next: value => console.log('Valor:', value), // Publicación de datos
      error: err => console.error('Error:', err),  // Notificación de error
      complete: () => console.log('Completo')      // Notificación de finalización
    };
    
    const observable$ = of(1, 2, 3); // Crear Observable fácilmente
    
    // Usar objeto Observer
    observable$.subscribe(observer);
    
    // Observer parcial también es posible
    observable$.subscribe({
      next: value => console.log('Solo valor:', value)
    });
    
    // Formato de objeto (recomendado)
    observable$.subscribe({
      next: value => console.log('Valor::', value),
      error: err => console.error('Error:', err),
      complete: () => console.log('Completo::')
    });
    
    // Salida:
    // Valor: 1
    // Valor: 2
    // Valor: 3
    // Completo
    // Solo valor: 1
    // Solo valor: 2
    // Solo valor: 3
    // Valor:: 1
    // Valor:: 2
    // Valor:: 3
    // Completo::

    Subscription ​

    Subscription representa la ejecución de Observable y se usa principalmente para cancelar suscripción con unsubscribe().

    Ejemplo ​

    ts
    import { interval } from 'rxjs';
    import { take } from 'rxjs';
    
    const numbers$ = interval(1000).pipe(take(5));
    
    // Mantener suscripción
    const subscription = numbers$.subscribe({
      next: value => console.log('Valor:', value),
      complete: () => console.log('Completo')
    });
    
    // Cancelación manual de suscripción después de 3 segundos
    setTimeout(() => {
      subscription.unsubscribe(); // Cancelar suscripción
      console.log('Suscripción cancelada');
    }, 3000);
    
    // Salida:
    // Valor: 0
    // Valor: 1
    // Valor: 2
    // Suscripción cancelada

    Manejo de Errores ​

    En el ciclo de vida de Observable, llamar a error() termina inmediatamente el flujo, y complete() no se llama. Por eso el uso de catchError y el diseño de retry son importantes.

    Ejemplo ​

    ts
    import { Observable, of } from 'rxjs';
    import { catchError, retry } from 'rxjs';
    
    // Observable que genera un error
    const failingObservable$ = new Observable<number>(subscriber => {
      subscriber.next(1);
      subscriber.next(2);
      subscriber.error(new Error('Error intencional'));
      // Tenga en cuenta que complete no se llama después del error
    });
    
    // Ejemplo de manejo de errores
    failingObservable$.pipe(
      // Reintentar 3 veces cuando ocurra un error
      retry(3),
      // Si aún ocurre un error, cambiar a Observable alternativo
      catchError((error: unknown) => {
        console.error('Error capturado:', (error instanceof Error ? error.message : String(error)));
        return of('Valor alternativo después del error');
      })
    ).subscribe({
      next: value => console.log('Valor:', value),
      error: err => console.error('Error no manejado:', err),
      complete: () => console.log('Completo')
    });
    
    // Salida:
    // Valor: 1
    // Valor: 2
    // Valor: 1
    // Valor: 2
    // Valor: 1
    // Valor: 2
    // Valor: 1
    // Valor: 2
    // Error capturado: Error intencional
    // Valor: Valor alternativo después del error
    // Completo

    Ciclo de Vida de Finalización ​

    La finalización de un Observable ocurre cuando se llama explícitamente a complete() o cuando un flujo finito termina.

    Ejemplo ​

    ts
    import { of, interval, Observable } from 'rxjs';
    import { take } from 'rxjs';
    
    // Observable finito (se completa automáticamente)
    const finite$ = of(1, 2, 3);
    finite$.subscribe({
      next: value => console.log('Valor finito:', value),
      complete: () => console.log('Observable finito completado')
    });
    
    // Convertir Observable infinito a finito
    const limited$ = interval(1000).pipe(take(3));
    limited$.subscribe({
      next: value => console.log('Valor limitado:', value),
      complete: () => console.log('Observable limitado completado')
    });
    
    // Observable completado manualmente
    const manual$ = new Observable<number>(subscriber => {
      subscriber.next(1);
    
      setTimeout(() => {
        subscriber.next(2);
        subscriber.complete(); // Completar explícitamente
      }, 2000);
    });
    
    manual$.subscribe({
      next: value => console.log('Valor manual:', value),
      complete: () => console.log('Observable manual completado')
    });
    
    // Salida:
    // Valor finito: 1
    // Valor finito: 2
    // Valor finito: 3
    // Observable finito completado
    // Valor manual: 1
    // Valor limitado: 0
    // Valor limitado: 1
    // Valor manual: 2
    // Observable manual completado
    // Valor limitado: 2
    // Observable limitado completado

    Gestión de Recursos y Prevención de Fugas de Memoria ​

    La cancelación de suscripción en el momento apropiado es importante para prevenir fugas de memoria.

    Ejemplo ​

    ts
    import { interval, Subject } from 'rxjs';
    import { takeUntil } from 'rxjs';
    
    // Imitando ciclo de vida de componente
    class Component {
      private destroy$ = new Subject<void>();
    
      constructor() {
        // Intervalo cada 1 segundo (causa potencial de fuga de memoria)
        interval(1000).pipe(
          // Cancelar suscripción automáticamente cuando se destruye el componente
          takeUntil(this.destroy$)
        ).subscribe(value => {
          console.log('Valor en componente:', value);
        });
      }
    
      // Destrucción de componente
      ngOnDestroy() {
        console.log('Componente destruido');
        this.destroy$.next();
        this.destroy$.complete();
      }
    }
    
    // Ejemplo de uso
    const component = new Component();
    
    // Destruir componente después de 5 segundos
    setTimeout(() => {
      (component as any).ngOnDestroy();
    }, 5000);
    
    // Salida:
    // Valor en componente: 0
    // Valor en componente: 1
    // Valor en componente: 2
    // Valor en componente: 3
    // Valor en componente: 4
    // Componente destruido

    Resumen ​

    Entender el ciclo de vida de Observable le permite:

    • Liberar recursos en el momento apropiado
    • Implementar estrategias de manejo de errores y recuperación
    • Diferenciar entre Observables cold y hot
    • Gestionar efectos secundarios

    En particular, los frameworks basados en componentes como Angular y React requieren gestión de suscripciones junto con el ciclo de vida, aprovechando takeUntil, unsubscribe, finalize, etc.

    Publicado bajo licencia CC-BY-4.0.