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.
Questo articolo fa parte della serie “Introduzione RxJS”:
- Parte 4: Subscription & Unsubscribe (questo articolo)
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 metodongOnDestroy
, 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 pipeasync
, che si occupa di effettuare la sottoscrizione direttamente nel template HTML –{{observable | async}}
– e automaticamente di invocare dietro le quinte la funzioneunsubscribe
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
Leave A Comment