Nel recente tutorial sullo sviluppo Web Elaborazione asincrona in ES6, abbiamo appreso un modo per elaborare diverse chiamate asincrone di seguito utilizzando il metodo statico Promise.all() metodo. Combina un iterabile di promesse in un’unica promessa che fornisce i risultati amalgamati come un unico array. Da quando è stato pubblicato quell’articolo, diverse persone mi hanno chiesto se esistesse un’alternativa RxJS pura per ottenere la stessa cosa che non si basasse sulle promesse. La risposta, come vedremo in questo tutorial, è un sonoro sì! Mentre ci siamo, tratteremo come associare dati asincroni a una variabile locale, come quella che memorizza un ID, in modo che tutte le informazioni per una determinata entità siano mantenute insieme. In caso contrario, elaborando numerose chiamate RxJS all’interno di una singola sottoscrivi() il callback può comportare un’alterazione della continuità sia sgradevole che insidiosa. Per dimostrare come e perché ciò può accadere, recupereremo un elenco di etichette di collegamento due volte, sia con che senza associazione di dati, e assisteremo ai risultati molto diversi.
Prima di andare avanti, potresti voler rivisitare (o leggere) Elaborazione asincrona in ES6.
Effettuare chiamate asincrone pure RxJS all’interno di un loop
La soluzione presentata nel tutorial Elaborazione asincrona in ES6 utilizzava il Promise.all() metodo per verificare la validità di più collegamenti. Ecco di nuovo quel metodo, ai fini della ricapitolazione:
public async hideTheBadLinks() { Promise.all(this.links.map(link => this.isValidLink(link))) .then(results => this.links = links.filter((_, i) => results[i])); }
Non c’è niente di sbagliato nel codice sopra di per sé; funziona esattamente come previsto. Non c’è, tuttavia, alcuna protezione contro le perdite di memoria, come potremmo involontariamente produrre se dovessimo passare a un’altra pagina. In tal caso, la sottoscrizione rimarrebbe anche dopo la distruzione del componente, in modo che, tornando alla pagina, si richiamerebbe nuovamente il costruttore del componente e genererebbe una nuova sottoscrizione.
Nel frattempo, l’utilizzo degli operatori RxJS comporta dei rischi, se utilizzati in modo errato: principalmente che i payload asincroni potrebbero dissociarsi dai relativi ID. Per capire come ciò potrebbe accadere, osserva la seguente soluzione (difettosa):
private ngUnsubscribe = new Subject(); // create an array with values from 1 to 5 public linkIds = [...Array(5)].map((x,i) => i+1); public linkNames: string[]; constructor() { from(this.linkIds) .pipe( mergeMap<number, Observable<string>>(id => this.getLinkName(id)), toArray<string>(), takeUntil(this.ngUnsubscribe) ).subscribe(linkNames => this.linkNames = linkNames); } public ngOnDestroy() { this.ngUnsubscribe.next(); this.ngUnsubscribe.complete(); }
Sebbene la soluzione di cui sopra garantisca che il nostro abbonamento termini quando il componente viene distrutto, introduce un problema completamente nuovo. Il mergeMap l’operatore non attende il completamento dell’osservabile interno precedente prima di attivare l’osservabile interno successivo, consentendo a più osservabili interni di emettere valori in parallelo. Sotto carico pesante, o con chiamate rapide, è possibile che le richieste vengano elaborate fuori servizio!
Un approccio molto migliore è quello di utilizzare forkUnisciti. Esegue tutte le sequenze osservabili in parallelo e raccoglie i loro ultimi elementi. Ciò significa che l’operatore ottiene valori da osservabili completati e restituisce un osservabile completato con valore singolo. Ciò preserva l’ordine originale e rende superfluo l’annullamento dell’iscrizione! Nota quanto è più semplice la seguente implementazione rispetto all’ultima:
forkJoin<Observable<string>[]>( this.linkIds.map<Observable< string>>( linkId => this.getLinkName(linkId) ) ).subscribe(linkNames => this.linkNames = linkNames);
Leggere: RxJS Observables Primer in angolare
Una soluzione ancora migliore
A questo punto dovrebbe essere evidente che l’archiviazione di ID locali e dati associati recuperati come array paralleli separati non è l’approccio più affidabile. Nell’elaborazione asincrona, gli attributi degli oggetti devono essere tenuti insieme il più possibile. Con questo in mente, eseguiamo il refactoring del file forkUnisciti codice per memorizzare entrambi i link id e nome in un oggetto.
Per prima cosa, definiamo un’interfaccia che delinea la struttura dell’oggetto:
interface Link { id: number; name: string; }
Possiamo ora dichiarare un array di CollegamentoS:
public links: Link[];
Ora per recuperare i nomi dei collegamenti nel modo CORRETTO, in modo che gli ID e i nomi siano associati insieme. Per fare ciò, convogliamo l’output di getLinkName() all’RxJS carta geografica operatore. Oltre a ricevere il linkName come parametro, la funzione di callback della mappa ha accesso anche al linkId grazie alla chiusura formata da esterno Array.map funzione di richiamata:
forkJoin<Observable<Link>[]>( this.linkIds.map<Observable<Link>>( linkId => this.getLinkName(linkId) .pipe<Link>( map<string, Link>(linkName => ({ id: linkId, name: linkName }) ) ) ) ).subscribe(links => this.links = links);
Leggere: Esecuzione di osservabili RxJS in ordine
Accesso ai dati di collegamento nel modello
La memorizzazione dei dati di collegamento negli oggetti influisce sul modo in cui vi accediamo all’interno del modello. In precedenza, i nomi dei collegamenti erano associati ai loro ID utilizzando il ciclo indiceche è stato passato al getLinkInfo() metodo:
<ul> <li *ngFor="let linkName of linkNames; let i=index"> <a role="button" (click)="getLinkInfo(i)" href="https://www.htmlgoodies.com/javascript/binding-async-data-rxjs/javascript:void(0)"> {{linkName}} </a> </li> </ul>
La memorizzazione dei dati di collegamento negli oggetti elimina la necessità di un ciclo indice poiché possiamo passare l’oggetto direttamente a getLinkInfo(). Il nome del collegamento è accessibile tramite l’oggetto collegamento nome attributo:
<ul> <li *ngFor="let link of links"> <a role="button" (click)="getLinkInfo(link)" href="https://www.htmlgoodies.com/javascript/binding-async-data-rxjs/javascript:void(0)"> {{link.name}} </a> </li> </ul>
Avere tutti gli attributi di collegamento all’interno dello stesso oggetto rende il getLinkInfo() anche il lavoro del metodo è molto più semplice:
public getLinkInfo(link: Link) { this.linkInfo = 'You clicked the link with id ' + link.id + ' and name "' + link.name + '".'; }
La demo
SU codepen.iotroverai una demo che mette il file mergeMap e forkUnisciti Soluzioni RxJS insieme per un facile confronto. Guarda cosa succede quando facciamo clic su uno dei collegamenti nella prima riga (non valida):
A causa dell’inclusione di ID link nei loro nomi, è facile individuare le incongruenze. Se gli ID non facevano parte dei nomi dei link, la mancata corrispondenza potrebbe continuare fino a quando qualcuno non si è reso conto che qualcosa non va!
Considerazioni finali sull’associazione di dati asincroni con RxJS
In questo tutorial abbiamo ottenuto un doppio scherzo della bontà di RxJS: come elaborare diverse chiamate asincrone di seguito utilizzando gli operatori RxJS mentre si legano i dati recuperati ai loro ID, in modo che tutte le informazioni per una determinata entità siano mantenute insieme.
Leggi di più Tutorial di programmazione JavaScript e sviluppo web.