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.