Introduzione RXJS – parte 1: fundamentals

Home/code/javascript/Introduzione RXJS – parte 1: fundamentals

Introduzione RXJS – parte 1: fundamentals

Occupandomi per lavoro di formazione front-end, gestendo diverse community, organizzando e partecipando a diversi eventi Javascript, ho notato che uno degli ostacoli maggiori, in particolar modo se si utilizza Angular, è rappresentato da RxJS, dipendenza fondamentale del framework.

L’approccio reattivo è utilizzato anche da molti sviluppatori NodeJS, Java, C#, ecc. (versioni disponibili) ma, almeno per quanto mi riguarda, anche in connubio con React/Redux o in vanilla JS. Di conseguenza, oggi giorno, è molto probabile che ci troveremo a lavorare, a leggere documentazione o porzioni di codice che sfruttano questo paradigma e fanno uso di Observable.

Ho deciso quindi di scrivere questa breve serie di 5 articoli introduttivi su RxJS e sul paradigma funzionale reattivo, dalle fondamenta all’utilizzo degli operatori, con un po’ di teoria, esempi pratici e diverse considerazioni personali nella speranza possa risultare utile a molti colleghi.

Ovviamente, questa serie non rappresenta una guida esaustiva su RxJS ma il mio obiettivo è semplicemente quello di incuriosire gli sviluppatori che ancora non la utilizzano e/o fornire un semplice punto di partenza per iniziare a comprendere la sua terminologia e il suo funzionamento, che inizialmente può sembrare contorto, per molti quasi overkill, ai limiti del masochismo : )

Non sono mai stato un fanatico della programmazione funzionale ma, devo ammettere, che negli ultimi anni mi sono avvicinato molto a questo paradigma grazie all’evoluzione che Javascript ha avuto da ES6 in poi e soprattutto grazie proprio all’utilizzo di RxJS in Angular e in state manager come NGRX.
Ad un certo punto, mi scontravo troppo spesso con codice reattivo, Observable, Subject e operatori RxJS che non comprendevo e ho dovuto necessariamente approfondire l’argomento, in primis perché da sviluppatore avevo la necessità di scrivere e comprendere il codice dei colleghi e, successivamente, da formatore, per poter trasferire determinati concetti agli studenti.

COS’È RXJS?

Nella documentazione ufficiale di RxJS possiamo leggere che :

RxJS is a library for composing asynchronous and event-based programs by using observable sequences. It combines the Observer pattern with the Iterator pattern and functional programming to fill the need for an ideal way of managing sequences of events.

Tutto chiaro eh? No?
La documentazione RxJS è ricca di definizioni complesse che, almeno inizialmente, sono davvero poco comprensibili. Ma facciamo un po’ di chiarezza e cerchiamo di semplificare quanto meno i concetti fondamentali.

RxJS, in sostanza, è una libreria per gestire eventi asincroni tramite l’utilizzo del concetto di Observarble e del paradigma funzionale (reattivo).

Quando parliamo di eventi asincroni non facciamo solo riferimento a chiamate HTTP ma anche a timer o eventi generati dal DOM tramite iterazioni utente, come ad esempio nello snippet seguente, in cui verrà eseguita una callback ad ogni click dell’utente.

Per gestire un evento click, in “vanilla” Javascript, solitamente si utilizza il metodo addEventListener che accetta una callback come secondo argomento:

document.addEventListener('click', () => console.log('Clicked!'));

Usando RXJS è possibile, ad esempio, creare un Observable tramite un’apposita funzione, chiamata operatore, che in questo specifico esempio è fromEvent e richiede due argomenti: un “target”, ovvero una selezione stile JQuery (ho i brividi solo a menzionarlo 😛 ), e il nome dell’evento da monitorare.

import { fromEvent } from 'rxjs';

fromEvent(document, 'click').subscribe(() => console.log('Clicked!'));

Tuttavia, prima di addentrarci, nei prossimi articoli, nel “magico mondo degli Observable” cerchiamo di comprendere almeno le basi e la terminologia.

I CONCETTI FONDAMENTALI

I costrutti su cui si basa RxJS sono i seguenti:

1. Observable

Una sorta di stream di informazioni che contiene dati sincroni e asincroni. Possiamo sottoscrivere questo stream (tramite il metodo subscribe) e ricevere passivamente i dati non appena sono emessi.

myObservable.subscribe(value => console.log(value))

2. Observer

Possiamo definire un Observer come un “consumer” che riceve valori emessi da un’Observable. Gli Observer sono semplici oggetti con 3 callbacks: next, error, complete che ricevono notifiche dall’Observable

myObservable.subscribe({
  next(value) { console.log('value ' + value); }, // invocato quando un valore viene emesso
  error(error) { console.log(error); },       // in caso di errori (di vario genere)
  complete() { console.log('completed')} .    // quando l'observable termina di emettere dati
});

ma che può essere spesso scritto in una forma più concisa:

myObservable.subscribe(
  value => console.log('value: ' + value),
  error => console.log(error),
  () => console.log('completed'),
);

3. Subscription

Una Subscription è un riferimento all’esecuzione di un Observable ed è utilizzato, principalmente, per terminare la sua esecuzione tramite il metodo unsubscribe. Ogni observer ha un’esecuzione indipendente dell’Observable rispetto le altre e questa modalità di funzionamento viene definita “unicast”.

Cosa intendo con “unicast”? Che due sottoscrizioni differenti allo stesso Observable sostanzialmente saranno indipendenti l’una dall’altra e non interferiranno tra di loro

const subscription1 = observable.subscribe(x => console.log(x))
const subscription2 = observable.subscribe(x => console.log(x))
// ...
subscription1.unsubscribe()
// la seconda subscription continua la sua esecuzione in modo indipendente dalla prima

4. Operators

Funzioni “pure” che ricevono un Observable come input e ne restituiscono uno come output. Queste funzioni, chiamate operatori sono utili per manipolare i valori forniti dagli Observable o, ad esempio, per “trasformare” un Observable da un tipo ad un altro, e possono essere facilmente concatenati tramite un approccio dichiarativo.
Alcuni esempi di operatori molto utilizzati: map , filter, concat, reduce, molto simili ai relativi metodi utilizzati in Javascript per manipolare gli array, altri molto particolari, “odiati” prima e “amati” successivamente da molti sviluppatori, perché inizialmente un po’ complessi da comprendere ma poi potentissimi. Nel seguente esempio, l’operatore interval emette un valore intero che parte da 0 e si incrementa di uno ogni 1000ms (1 secondo):

interval(1000);
  .subscribe(
    value => console.log(value); // 0, 1, 2, .... N
  )

In precedenza abbiamo anche visto l’operatore fromEvent che emette dati come risultato di un evento. Questa tipologia di operatori vengono chiamati “Operatori di Creazione”, perché permettono di creare un Observable praticamente da ogni tipo di sorgente, ma ne esistono di diverse tipologie, alcuni dei quali saranno analizzati nei prossimi articoli.

Non sai cos’è una “funzione pura”?
Vi rimando alla lettura di un ottimo articolo di Eric Elliot: “Master the JavaScript Interview: What is a Pure Function?”

5. Subject

I Subject sono utilizzati per il “multicasting”, ovvero la possibilità di condividere l’esecuzione dell’observer e gestire sottoscrizioni multiple allo stesso Observable in modo appropriato utilizzando le differenti tipologie incluse in RxJS: BehaviorSubject, Subject, ReplaySubject, AsyncSubject.
Mi rendo conto che, almeno per il momento, questa definizione potrebbe non essere molto comprensibile ma, per il momento, cerchiamo di comprendere le fondamenta.

data$: Subject<number> = new Subject();
data$.next(Math.random());
// first subscription
data$.subscribe(value => console.log(value)
// second subscription (abbiamo la garanzia che arrivi sempre lo stesso valore perché multicast)
data$.subscribe(value => console.log(value)

6. Scheduler

Utilizzato per controllare quando una sottoscrizione deve cominciare e può essere impostato esplicitamente , utilizzando una delle varie tipologie di scheduler messi a disposizione da RxJS o in modo implicito da diversi operatori. Ad esempio gli operatori time-related come debounceTime, delay, throttleTime, timeout ecc. utilizzano di default l’asyncScheduler. Inizialmente (o probabilmente mai) non avrete bisogno di questa particolare funzionalità, quindi per il momento possiamo metterla in secondo piano.

 

PULL vs PUSH

La documentazione di RxJS definisce un Observable come segue:

Observables are lazy Push collections of multiple values.

“Pull” e “Push”, in Javascript, descrivono come un “Producer”, ovvero l’entità che genera dati, può comunicare con un “Consumer” (chi li consuma, come il termine chiaramente indica).

Ogni funzione Javascript funziona in modalità “pull”.

function foo() {
  return 1000;
}

console.log(foo());

La funzione produce dei dati e saremo noi, sviluppatori, che chiederemo esplicitamente di ottenere dei dati da questa funzione semplicemente invocandola.

In un sistema “Push” è invece il “Producer” che determina quando inviare i dati al “Consumer”, che assolutamente non è a conoscenza di quando li riceverà.

Le Promise sono uno dei concetti più noti di sistema “Push” nel mondo Javascript, il classico doSomething.then(fn), tanto per intenderci.

getData.then(value => console.log(value))

In modo simile, RxJS ruota attorno al concetto di Observable, un costrutto che può essere sottoscritto, con il metodo subscribe e che, come abbiamo visto, può emettere valori sincroni e asincroni.

myObservable.subscribe(value => console.log(value))

Tuttavia, al contrario delle Promise, un Observable non solo può emettere più di un valore ma possono essere emessi anche in tempistiche differenti. Paradossalmente, un Observable potrebbe emettere valori all’infinito!

UNA DOMANDA: PERCHÈ? 😀

Si, inizialmente anche io mi sono posto questa domanda.
Dopo i primi mesi di frustrazione, rabbia, angoscia e strazio ho iniziato a vedere la luce e vi assicuro che alla fine ne varrà la pena.

Quindi, perché usare un sistema così “complesso” per gestire semplicemente dei dati?

1) L’utilizzo di un unico approccio per gestire dati sincroni e asincroni rappresenta un gran vantaggio se paragonato alle diverse modalità utilizzate in “vanilla Javascript”: diversi approcci per gestire l’asincronia (Promise), per manipolare array, usare i timer, gestire eventi, XHR, ecc..

2) Il paradigma funzionale riduce la possibilità di errori nel codice e, in connubio con RxJS, rende il codice molto più espressivo, conciso, permette di evitare il fastidioso (e poco leggibile) effetto definito callback hell delle Promise e alla fine, una volta acquisita un po’ di confidenza, semplificherà notevolmente molte attività che risulterebbero invece complesse o lunghe da eseguire.

3) RxJS è ormai una soluzione adottata in diversi contesti, sia nel mondo front-end che back-end e, quindi, una skill che uno sviluppatore web senior, almeno secondo me, dovrebbe possedere. E’ infatti utilizzato anche in Node, Java, React (Redux Observable) e in moltissimi altri contesti.

4) Se sei uno sviluppatore Angular, ad un certo punto della tua vita lavorativa, sarai costretto ad “entrare” nel paradigma reattivo o ad utilizzare gli operatori RxJS. Perchè ? Perchè il framework è totalmente basato sul concetto di Observable: HttpClient, gli @Output EventEmitter, le guardie e gli eventi del router, i Reactive Forms (e potrei continuare) sono reattivi dalle fondamenta e di conseguenza troverai spesso codice scritto dai colleghi, articoli, librerie e documentazione che ne fa ampio uso.

5) Sempre gli sviluppatori Angular, nel momento in cui decideranno (perché è sicuro che ad un certo punto della loro vita accadrà 😛 ) di usare uno state manager, come ad esempio NGRX, si troveranno di fronte ad un codice totalmente reattivo e sarà davvero difficile da comprendere se non si avrà una conoscenza, anche minima, di RxJS.

Nei prossimi articoli approfondiremo il concetto di Observable e il suo funzionamento ma solo nel quinto e ultimo articolo, sugli operatori RxJS, avrete la sensazione di comprendere meglio i vantaggi di questo approccio. Per il momento, mi rendo conto che può sembrare solo una complicazione ulteriore e l’ennesimo argomento (complicato e overkill) da studiare tra le decine e centinaia che già abbiamo nella nostra wish-list.

Se vuoi leggere il contenuto degli altri articoli iscriviti alla mia newsletter

2019-10-21T22:24:12+00:00 settembre 15th, 2019|code, javascript|

Leave A Comment