66 Promise.all
analizziamo il metodo all di promise, in modo da poter eseguire diverse promise in parallelo e ottenere il risultato di queste promise memorizzate in un array, oppure se qualcuna di queste promise viene rigettata, anche le altre verranno fermate.
La sintassi è molto semplice, si lancia Promise.all e si passa come parametro un array di promise che devono essere risolte
Promise.all(iterable)
Vediamo un caso pratico utilizzando sempre jsonplaceholder, e prendiamo l’url dei posts
https://jsonplaceholder.typicode.com/posts
concateneremo gli url a seconda del bisogno per il numero del post o i commenti ecco un esempio di url dei commenti per il primo post
https://jsonplaceholder.typicode.com/posts/1/comments
Creiamo una promise per prendere tutti i posts, poi aggiungiamo il post numero 1 e poi un’altra per prelevare i commenti. Noi vogliamo che entrambe le chiamate fatte in parallelo vengano concluse con esito positivo e solo allora processare i dati. Utilizzeremo il metodo fetch. Iniziamo creando una costante contenente l’url
const JSONPLACEHOLDER = 'https://jsonplaceholder.typicode.com/posts';
creiamo una promise con fetch per prelevare il post numero 1, memorizzandola in una variabile che chiameremo post, aggiungiamo un dollaro finale al nome della variabile ( $ ) che è una convenzione di uso comune per indicare che tale variabile contiene una promise o nel caso degli osservarle è un osservable, non un valore ancora risolto
let post$ = fetch(JSONPLACEHOLDER + '/1')
Ricordiamoci che questa chiamata di fetch ritorna una response, la quale al suo interno contiene il JSON nel body di cui vogliamo catturarne i dati, quindi concatenando il metodo then che avrà una risposta di tipo response di cui ci faremo restituire il suo metodo json() (della response), assegneremo la promise restituita alla variabile post$
.then(resp => resp.json() );
In modo identico creiamo la seconda chiamata che restituirà la promise per i commenti
let comments$ = fetch(JSONPLACEHOLDER + '/1/comments') .then( resp => resp.json() );
Ora andiamo a testare semplicemente le chiamate con
post$.then()
in modo da risolvere la promise contenuta in post$. Passiamo a then l’elenco dei post ricevuti usando la funzione freccia che passerà il post in console
post$.then( post => console.log(post));
stessa cosa per i commenti
comments$.then( comments => console.log(comments));
Controllando la console vedremo le 2 chiamate funzionanti con la restituzione dei valori, per la prima chiamata i dati del post 1 , per la seconda i commenti del post numero 1
Andando nella sezione Network dell’inspector del browser e selezionando il tab XHR vedremo le 2 chiamate AJAX refreshando la pagina. Cliccando su ogni chiamata vedremo i dati restituiti in formato JSON.
Adesso che abbiamo appurato che le chiamate funzionano, commentiamo i 2 test e andiamo ad utilizzare Promise.all che riceverà un array contenente le 2 variabili che hanno le 2 promise
Promise.all([post$, comments$])
Infine il then avrà come risposta l’array con le promise soddisfatte entrambe, indipendentemente da quale promise parte prima o dopo
.then( resp => { console.log(resp); } )
In console riceverò ora l’array di risposta contenente le 2 promise fetch risolte, ecco il codice completo
const JSONPLACEHOLDER = 'https://jsonplaceholder.typicode.com/posts'; let post$ = fetch(JSONPLACEHOLDER + '/1') .then( resp => resp.json() ); //post$.then( post => console.log(post)); let comments$ = fetch(JSONPLACEHOLDER + '/1/comments').then( resp => resp.json() ); //comments$.then( comments => console.log(comments)); Promise.all([post$, comments$]) .then( resp => { console.log(resp); } )
Ciclare i dati
Ora possiamo inserire i dati nell’ HTML,
<!DOCTYPE html> <html lang="it"> <head> <meta charset="UTF-8"> <title>Promise all</title> <script src="fetchall.js"></script> </head> <body> </body> </html>
creiamo un <div> contenente un <h1> con id postTitle e un <div> con id postBody e una lista con id comments
<!DOCTYPE html> <html lang="it"> <head> <meta charset="UTF-8"> <title>Promises</title> <script src="fetchall.js"></script> </head> <body> <div>> <h1 id="postTitle"></h1> <div id="postBody"></div> </div> <ul id="comments"> </ul> </body> </html>
nel metodo then del Promise.all nel file javascript eliminiamo la verifica in console e creiamo una variabile post che contiene l’indice zero dell’array (cioè contiene i dati del nostro post)
Promise.all([post$, comments$]) .then( resp => { let post = resp[0]; } )
ora andiamo ad inserire col querySelector nell’h1
document.querySelector('#postTitle')
aggiungiamo l’HTML con innerHTML le chiavi body e title recuperate dall’inspector del browser
let post = resp[0]; document.querySelector('#postTitle').innerHTML = post.title; document.querySelector('#postTitle').innerHTML = post.body;
Per i commenti che si trovano alla posizione 1 dell’array Promise.all, utilizziamo un ciclo foreach
let comments = resp[1]; comments.forEach( comment => { let li = document.createElement('li'); })
aggiungiamo la chiave body tratta dal valore 1 dell’array vista dall’inspector
li.innerHTML = comment.body;
aggiungiamo in testa una variabile ul che seleziona l’ul del nostro HTML
let ul = document.querySelector('#comments');
adesso con ul effettuiamo un appendChild passandogli li. Abbiamo inserito la descrizione e i commenti nel file HTML.
Ecco il codice js completo
const JSONPLACEHOLDER = 'https://jsonplaceholder.typicode.com/posts'; let post$ = fetch(JSONPLACEHOLDER + '/1') .then( resp => resp.json() ); let comments$ = fetch(JSONPLACEHOLDER + '/1/comments').then( resp => resp.json() ); Promise.all([post$, comments$]) .then( resp => { let post = resp[0]; document.querySelector('#postTitle').innerHTML = post.title; document.querySelector('#postBody').innerHTML = post.body; let comments = resp[1]; let ul = document.querySelector('#comments'); comments.forEach(comment => { let li = document.createElement('li'); li.innerHTML = comment.body; ul.appendChild(li); }) } )
Ovviamente poi si dovrebbe sistemate col CSS per dare un aspetto gradevole, ma abbiamo visto come generare dinamicamente una pagina web estraendo i dati con API e chiamate AJAX con Promise fetch e Promise.all. Qualora ci fosse qualche errore sollevato anche da 1 delle promise, dobbiamo gestirlo concatenando l’eccezione catch in fondo
).catch(err =>console.log(err));
Solleviamo l’eccezione throw nel primo then dei commenti, commentando la restituzione del json
let post$ = fetch(JSONPLACEHOLDER + '/1') .then( resp => { throw new Error('Errore contattando il server')}//resp.json() );
In questo modo le promise non verranno risolte ed abbiamo simulato un errore del server che ci verrà restituito refrashando la pagina.
Concludendo, la promise.all è molto utile quando vogliamo effettuare più chiamate contemporaneamente e vogliamo processare le risposte solo quando tutte sono risolte.
Un esempio pratico sono una chiamata per ottenere l’elenco delle regioni, un altra per le provincie e una per le città, ci interessa processare i risultati quando tutte le chiamate sono andate a buon fine.