Anche se presti attenzione a ripulire gli abbonamenti quando i componenti vengono distrutti, c’è ancora il rischio di perdite di memoria dagli abbonamenti RxJS annidati. In effetti, ci sono anche altri problemi con gli abbonamenti nidificati, come i problemi di temporizzazione. Per questi motivi, gli abbonamenti nidificati hanno guadagnato il primo posto nell’elenco degli anti-pattern RxJS. Molti sviluppatori usano ancora gli abbonamenti nidificati semplicemente perché non sanno come fare rifattorizzare loro per evitare la nidificazione. È un problema complesso, per il quale non esiste una soluzione valida per tutti. In effetti, la strada per una migliore gestione degli abbonamenti RxJS è studiare numerosi esempi di utilizzo “corretto”. Sfortunatamente, i buoni esempi scarseggiano. Pertanto, ne aggiungeremo un altro alla pila in questo tutorial. In questo caso, prenderemo del codice reale (anche se leggermente riscritto per essere più generico) le cui sottoscrizioni nidificate risultano da condizioni e lo riscriviamo senza l’annidamento.
Procedura dettagliata del codice
Il codice che esamineremo qui oggi proviene da un’applicazione reale che raccoglie articoli di notizie, post sui social media e singoli blog pubblicati online su azioni e altri strumenti di investimento. L’applicazione fornisce quindi analisi che consentono agli investitori di ottenere una visione affidabile di la follal’impatto sulle loro partecipazioni.
Il codice in questione recupera i dati per la schermata del cruscotto, nella foto sopra:
this.userInfoService.userPermissions.subscribe(( permissions) => { this.isLimited = permissions.preLogin; if (!this.isLimited) { this.trackerService. recordPageView( PageViewCategory.dashboard); this.dashboardService .takeUntil(this.ngUnsubscribe) .subscribe((e) => { const id = e.entityId; if (id) { this.apiService. getArticleStats(id).subscribe( (article) => { if (article != null) { this.entity = article.entity; // Set current entity for dashboard page this.dashboardService. setCurrentEntity(article. entity); if (this.entity?.instrumentId) { this.loadInstrumentData(this. entity.instrumentId); } } } ); } });
Ce ne sono tre SE dichiarazioni che determinano se sottoscrivere o meno un altro servizio:
- Se l’accesso dell’utente non è limitato
- Se l’entità (e) ha un identità
- Se l’articolo ha un’entità associata
Eliminare le dichiarazioni IF
Il flusso di controllo del codice sopra non è diverso da questo esempio che mi sono imbattuto su stackoverflow.com, che ha anche sottoscritto più servizi in base al risultato di un’istruzione IF:
source.subscribe(x => { doAnyway(x); if (x.Id) { doSometing(x); } else { // id Not set, get default Id this.idService.getDefault().subscribe(id => { x.Id = id; doSometing(x); }); } });
In quel caso, un intervistato ha suggerito la seguente struttura che impiega l’RxJS rubinetto() e switchMap() operatori per sostituire la struttura del codice precedente:
source.pipe( tap(val => doAnyway(val)), switchMap(val => val.id ? of(val.id) : this.idService.getDefault()) ).subscribe(id => { this.id = id; doSomething(id); });
A quanto pare, questi due operatori funzioneranno altrettanto bene per noi!
RxJS tap() e switchMap() spiegato
Come suggerisce il nome, rubinetto() esegue un effetto collaterale per ogni emissione sulla sorgente Observable, e restituisce un Observable identico alla sorgente. Quindi, è perfetto per fare qualcosa con un osservabile in cui non si desidera alterarlo in alcun modo.
Possiamo usare rubinetto() per impostare il è limitato bandiera come segue:
this.userInfoService.userPermissions .pipe( takeUntil(this.ngUnsubscribe), tap(permissions => this.isLimited = permissions.preLogin) ) .subscribe((article) => { // etc... });
Passando a switchMap(), è una delle numerose funzioni di mappatura RxJS che funzionano ciascuna in modo leggermente diverso. Il switchMap() La funzione crea un osservabile derivato (chiamato osservabile interno) da un osservabile di origine ed emette quei valori. Ogni volta che la sorgente emette un nuovo valore, switchMap() creerà un nuovo osservabile interno e passerà invece a quei valori. Gli osservabili interni che vengono creati al volo vengono annullati dalla sottoscrizione, lasciando l’osservabile di origine aperto per emettere più valori.
Il trucco per usare le funzioni di mappatura è che dobbiamo restituire l’osservabile successivo nella catena. Nel nostro caso, dobbiamo passare il dashboardService insieme per il prossimo passo. Se l’utente ha un accesso limitato, possiamo restituire un vuoto Parametri Dashboard oggetto e creare un nuovo osservabile da esso utilizzando RxJS di() funzione:
this.userInfoService.userPermissions .pipe( takeUntil(this.ngUnsubscribe), tap(permissions => this.isLimited = permissions.preLogin) ), switchMap(() => { if (this.isLimited) { return of(new DashboardParams()); } else { this.trackerService. recordPageView( PageViewCategory.dashboard); return this.dashboardService } }) ) .subscribe((article) => { // etc... });
Dobbiamo ancora chiamare il apiService per ottenere i dati dell’articolo, quindi ora dobbiamo elaborare il Parametri Dashboard e restituire il apiService.getArticleStats’s emesso Osservabile. Ancora, switchMap() è la funzione per farlo. Se l’Entità emessa (e) ha un identità, possiamo quindi procedere alla chiamata apiService.getArticleStats(). Altrimenti, possiamo restituire di nuovo un osservabile che emette un vuoto Dati articolo oggetto, che è apiService.getArticleStats()‘ tipo emesso. Infine, i dati dell’articolo vengono elaborati nel sottoscrivi() gestore:
this.userInfoService.userPermissions .pipe( takeUntil(this.ngUnsubscribe), tap((permissions) => { this.isLimited = permissions.preLogin; }), switchMap(() => { if (this.isLimited) { return of(new DashboardParams()); } else { this.trackerService. recordPageView( PageViewCategory.dashboard); return this.dashboardService } }), switchMap((e: DashboardParams) => { const id = e.entityId; return id ? this.apiService. getArticleStats(id) : of(<ArticleData>{}) }) ) .subscribe((article) => { if (article != null) { this.entity = article.entity; // Set current entity for dashboard page this.dashboardService. setCurrentEntity(article. entity); if (this.entity.instrumentId) { this.loadInstrumentData(this. entity.instrumentId); } } });
Conclusione
Con circa un miliardo di operatori, RxJS è senza dubbio lo strumento più potente per lavorare con codice asincrono o basato su callback. È anche il più complicato. Come accennato nell’introduzione, il refactoring degli abbonamenti nidificati non è facile, poiché ogni caso deve essere valutato in base ai propri requisiti individuali. Si spera che l’esempio presentato qui oggi aiuti a strutturare il proprio codice in un modo più corretto ed efficiente.