68 Funzioni async con Promise all e array map
In questa lezione approfondiamo lo studio di sync await combinandolo con Promise.all.
Riferendoci alla lezione precedente, supponiamo di voler avere i dati dell’utente per ogni album. Aprendo la pagina degli album di json place holder
https://jsonplaceholder.typicode.com/albums
vediamo che abbiamo lo userId, ma non i dati dell’utente come nome e cognome. Riportando il codice precedente per dare continuità:
const albumUrl = 'https://jsonplaceholder.typicode.com/albums'; const photoUrl = 'https://jsonplaceholder.typicode.com/photos'; async function fetchAlbum(id) { const album = await fetch(albumUrl + '/' + id).then(resp => resp.json()) .then(albumcontent => albumcontent); const album2 = fetch(albumUrl + '/' + 2).then(resp => resp.json()) .then(albumcontent => albumcontent); console.log(album2); return album; } let res = fetchAlbum(1); res.then( resp => console.log(resp));
aggiungiamo una nuova url che prende gli utenti
const userUrl = 'https://jsonplaceholder.typicode.com/users';
adesso commentando il precedente codice, prendiamo tutti gli albums con una funzione async await
async function fetchAlbums() { const albums = await fetch(albumUrl).then(resp => resp.json()) .then(albumcontent => albumcontent); return album; } let res = fetchAlbums(); res.then( resp => console.log(resp));
a questo punto abbiamo recuperato tutti gli albums con tutti gli userId, id, title, ma noi vogliamo i dati dello user, quindi dovremo prelevare gli userId di ogni album. L’obbiettivo è far ritornare un oggetto di questo tipo:
return {albums: albums, users: users};
Siccome abbiamo ottenuto gli albums memorizzati nella variabile albums restituiti sottoforma di array, possiamo mappare ciascuno di questi array e fare la chiamata per prendere ciascuno di questi utenti, utilizzando il metodo map
let userPromises = albums.map()
Il metodo map esegue una funzione per ogni elemento dell’array, restituendone l’elemento trasformato dalla funzione. Nel nostro caso restituiremo la fetch dello userId
let userPromises = albums.map(album => { return fetch(userUrl + '/' + album.userId ); })
ritorniamo la response col json del metodo then
let userPromises = albums.map(album => { return fetch(userUrl + '/' + album.userId ).then( resp => resp.json()); })
a questo punto abbiamo ottenuto un elenco di promeses contenute in userPromises, stampiamole per verifica in console e inizializziamo la costante users come array vuoto per evitare un errore
console.log(userPromises); const users = [];
a questo punto in console abbiamo un array di promises, ovvero per ogni utente stiamo effettuando una chiamata, cosa da sistemare perchè non dobbiamo fare centinaia di chiamate per recuperare gli utenti, in quanto molti id sono ripetuti.
Per ora risolviamo queste promises in contemporanea con Promise.all passandogli l’array di promeses userPromeses
users = Promise.all(userPromises);
In questo modo sfruttiamo il fatto che queste chiamate verranno effettuate in parallelo e sarà effettuata una chiamata per ogni utente.
Aggiungiamo await davanti, cosìcchè il codice andrà in pausa fintanto che tutte queste promises non saranno risolte, con il vantaggio che le chiamate avverranno in parallelo e non una ad una, come avveniva nel fetch di albumUrl.
users = await Promise.all(userPromises);
Facciamo un console.log di users per vedere i dati restituiti, avremo l’elenco degli utenti con i loro dati e gli albums
console.log(users);
adesso vediamo come migliorare il codice non facendo una chiamata per ogni utente di ogni album, ma solo per gli utenti univoci filtrando l’array degli albums per avere solo essi.
Istanziamo un array vuoto usersArray
let usersArray = [];
Invece che mappare nel modo precedente, usiamo il map su album restituendo solo gli utenti non presenti in questo array. Ragioniamo quindi in questo modo: se l’array usersArray non include album.userId, allora inseriamo questo userId nell’array col metodo push
albums.map( album => { if (!usersArray.includes(album.userId)) { usersArray.push(album.userId); } });
Una volta terminato questo map di albums, possiamo ciclare l’array userArray, oppure combiniamo quest’ultima logica con quella precedente, quindi se l’utente non è incluso nell’array temporaneo, allora faccio la chiamata.
1° esempio
let userPromises = albums.map(album => { if (!usersArray.includes(album.userId)) { usersArray.push(album.userId); return fetch(userUrl + '/' + album.userId ).then( resp => resp.json()); } })
La volta successiva che il codice passerà, vedrà che l’utente è incluso e non effettuerà la chiamata. Con questa soluzione nella console riceverò in console sempre un array di 100 promeses, molte delle quali sono undefined cosicché dove risulterà undefined non verrà effettuata la chiamata. Infatti spostandoci nel tab network noteremo che le chiamate effettive fatte saranno solo 11, la prima per tirare giù gli albums e altre 10 una per ogni utente diverso, dove il programma incontrerà undefined nell’array, la chiamata fetch non sarà effettuata. In pratica l’array userPromises avrà 100 posizioni al suo interno, ma solo 10 di questo sono delle fetch, gli altri sono valori promises che vengono risolti subito. L’unica parte ancora scorretta è che avremo numerosi posti di utenti inesistenti occupati nell’array. Tale inconsistenza la possiamo risolvere con un filter applicato a users. Rinominiamo la costante users in userAll e applichiamo il filtro dicendo che il tipo è diverso da undefined
const usersAll = await Promise.all(userPromises); let users = usersAll.filter( val => typeof val != 'undefined');
oppure si può mettere che val è diversa dalla costante undefined ( quindi senza apici )
let users = usersAll.filter( val => val != undefined);
otteniamo in entrambi i casi lo stesso risultato, ovvero un array contenete solo 10 promises.
2° esempio
Ripristiniamo il map iniziale di album e se lo userId non c’è lo inserisco col push
let usersArray = []; albums.map( album => { if (!usersArray.includes(album.userId)) { usersArray.push(album.userId); } });
a questo punto invece che mappare tutti gli albums come nel 1* esempio, mappo solo userArray
let userPromises = usersArray.map()
Siccome userArray avrebbe l’id degli utenti eseguo la chiamata fetch per ogni userId
let userPromises = usersArray.map(userId => fetch(userUrl + '/' + userId ).then( resp => resp.json()));
Ottenendo lo stesso risultato con un codice nettamente più pulito e corto rispetto al primo esempio, possiamo cancellare la riga del filter inserita nel primo esempio che ormai non serve a nulla
let users = usersAll.filter( val => val != undefined);
e lasciare solo
const users = await Promise.all(userPromises);
Questo è un esempio di come combinare async await che mette in pausa il codice con Promise.all che effettua chiamate in parallelo per ogni promises.