Introduzione RXJS – parte 4: Subscription & unsubscribe

Home/code/javascript/Introduzione RXJS – parte 4: Subscription & unsubscribe

Introduzione RXJS – parte 4: Subscription & unsubscribe

L’esecuzione di un Observable può essere potenzialmente infinita. Tuttavia, dobbiamo gestire due casistiche per evitare sprechi di memoria, potenziali bug o calcoli computazionali superflui.

1) un Observable potrebbe emettere dati in modo asincrono quando non più necessari, ad esempio dopo aver cambiato route in una Single Page Application in cui si era sottoscritto o dopo che un’eventuale componente Angular/React/Vue/ecc che lo ha sottoscritto sia stato eliminato dal DOM in precedenza. In questo caso, abbiamo la necessità di “cancellare” la sottoscrizione nel momento in cui non ne avremo più bisogno.

2) L’Observable ha terminato di emettere i dati e può essere quindi completato.

E’ fondamentale, quindi, gestire in modo opportuno il completamento dell’Observable e la cancellazione della sua esecuzione.

Subscription e unsubscribe

La funzione subscribe restituisce infatti un oggetto di sottoscrizione, definita Subscription:

const subscription = observable.subscribe(x => console.log(x));

Subscription fornisce un metodo unsubscribe, utile proprio a cancellare l’esecuzione di un Observable.

Nell’esempio seguente l’observer riceverà i primi due valori emessi dalle funzioni next() di riga 4 e 5 ma non riceveremo mai il valore random di riga 9, emesso dopo 1000ms, perché nell’ultima riga dello script annulliamo la sottoscrizione prima che venga emesso.

import { Observable } from 'rxjs';

const observable = new Observable(subscriber => {
  subscriber.next(1);
  subscriber.next(2);

  setInterval(() => {
    console.log('timer')
    subscriber.next(Math.random());
  }, 1000)
});


const subscription = observable.subscribe(
  (x) => console.log(x),
  err => console.error(err),
  () => console.log('completed')
); 

subscription.unsubscribe();

In Angular, la funzione unsubscribe è spesso inserita nel metodo ngOnDestroy, invocato automaticamente dal framework quando il componente viene distrutto (ad es. cambi di route, *ngIf ecc.) per cancellare eventuali esecuzioni in corso.

Non è invece necessario effettuato l’unsubscribe nel caso si utilizzi il pipe async, che si occupa di effettuare la sottoscrizione direttamente nel template HTML – {{observable | async}} – e automaticamente di invocare dietro le quinte la funzione unsubscribe quando il componente viene distrutto

Versione finale

Tuttavia, nell’esempio precedente, il setInterval rimane in esecuzione anche dopo aver cancellato la sottoscrizione (infatti il console.log('timer') continua a visualizzarsi all’infinito). L’obiettivo sarà ora:

1) simulare il completamento di un Observable dopo l’emissione di N valori e distruggere il timer.
2) distruggere l’esecuzione del timer quando si annulla una sottoscrizione all’Observable tramite la funzione unsubscribe, anche se invocato prima del suo completamento.

Come risolvere entrambi i problemi?

Il punto 1) è molto semplice da sistemare. Si potrebbe eseguire, quando necessario, sia un clearInterval che la funzione complete, per notificare l’observer che non saranno emessi più dati.

Per risolvere il punto 2), ma allo stesso tempo anche il punto 1), è possibile, semplicemente, specificare una funzione di ritorno dell’Observable.
Questa funzione sarà invocata automaticamente sia dopo il complete dell’Observable che dopo la cancellazione di una sottoscrizione.

Quindi sarà sufficiente inserire il clearInterval in questa funzione che sarà eseguita in entrambi gli scenari.

import { Observable } from 'rxjs'; 

const observable = new Observable(subscriber => {
  let count = 0;
  const id = setInterval(() => {
    console.log('timer')
    if (count > 2) {
      subscriber.complete();        // completiamo l'observable
    }
    subscriber.next(++count);
  }, 1000)

  return () => {                    // invocata al "complete" e dopo "unsubscribe"
    clearInterval(id),
    console.log('clear interval')
  }
});

const subscrition = observable.subscribe(
  (x) => console.log(x),
  (err) => console.log(err.message),
  () => console.log('completed')
);

// subscrition.unsubscribe();       // cancelliamo la sottoscrizione

Live Demo

2019-11-17T02:00:08+00:00 novembre 13th, 2019|code, javascript|

Leave A Comment