21 giugno 2021

Promise API

Esistono 6 metodi statici nella classe Promise. Qui copriremo rapidamente il loro casi d’uso.

Promise.all

Diciamo che vogliamo eseguire molte promise in parallelo, e aspettare che siano tutte pronte.

Per esempio, scaricare da diversi URL in parallelo e processare il contenuto quando abbiamo finito con tutti.

Ecco a cosa serve Promise.all.

La sintassi è:

let promise = Promise.all([...promises...]);

Promise.all accetta un array di promise (tecnicamente si può usare qualsiasi iterabile, ma solitamente si usa un array) e ritorna una nuova promise.

La nuova promise si risolve quando tutte le promise elencate vengono risolte, e l’array dei loro risultati diventa il risultato finale.

Per esempio, il Promise.all sotto si ferma (settles) dopo 3 secondi, ed il suo risultato è un array [1, 2, 3]:

Promise.all([
  new Promise(resolve => setTimeout(() => resolve(1), 3000)), // 1
  new Promise(resolve => setTimeout(() => resolve(2), 2000)), // 2
  new Promise(resolve => setTimeout(() => resolve(3), 1000))  // 3
]).then(alert); // 1,2,3 quando le promise sono pronte: ogni promise contribuisce con un membro dell'array

È da notare che l’ordine relativo rimane lo stesso. Anche se la prima promise prendesse il tempo più lungo per risolversi (resolve), il suo risultato sarà sempre il primo nell’array dei risultati.

Un trucco comune consiste nel mappare array of di dati da lavorare in un array di promise, per poi avvolgerli (to wrap) in Promise.all.

Per esempio, se abbiamo un array di URL, possiamo scaricarli tutti così:

let urls = [
  'https://api.github.com/users/iliakan',
  'https://api.github.com/users/remy',
  'https://api.github.com/users/jeresig'
];

// mappiamo tutti gli url con la promise ritornata da fetch
let requests = urls.map(url => fetch(url));

/// Promise.all attende fino a quando tutti i job sono risolti (resolved)
Promise.all(requests)
  .then(responses => responses.forEach(
    response => alert(`${response.url}: ${response.status}`)
  ));

Un esempio migliore in cui scarichiamo informazioni utente per un array di utenti di GitHub in base al loro nome (potremmo scaricare una matrice di merci in base ai rispettivi id, la logica è la stessa)

let names = ['iliakan', 'remy', 'jeresig'];

let requests = names.map(name => fetch(`https://api.github.com/users/${name}`));

Promise.all(requests)
  .then(responses => {
    // tutte le risposte sono pronte, possiamo mostrare i loro codici di stato HTTP
    for(let response of responses) {
      alert(`${response.url}: ${response.status}`); // mostra 200 per ogni url
    }

    return responses;
  })
  // mappa l'array di risposte in un array di response.json() per leggere il loro contenuto
  .then(responses => Promise.all(responses.map(r => r.json())))
  // è stato fatto il parsing JSON di tutte le risposte JSON: "users" è l'array con i risultati
  .then(users => users.forEach(user => alert(user.name)));

Se una qualsiasi delle promise è respinta (rejected), Promise.all viene immediatamente respinta (rejects) con l’errore.

Per esempio:

Promise.all([
  new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)),
  new Promise((resolve, reject) => setTimeout(() => reject(new Error("Whoops!")), 2000)),
  new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000))
]).catch(alert); // Error: Whoops!

Qui la seconda promise viene respinta (rejects) in due secondi. Questo porta al rigetto immediato di Promise.all, così .catch viene eseguito: l’errore del rigetto diventa il risultato di tutto Promise.all.

In caso di errore, le altre promise vengono ignorate

Se una promise è respinta (rejects), Promise.all è immediatamente respinto, dimenticando completamente delle altre nella lista. I loro risultati sono ignorati.

Per esempio, se ci sono molte chiamate fetch , come nell’esempio sopra, ed una di esse fallisce, le altre continueranno ad essere eseguite, ma Promise.all le ignorerà. Probabilmente poi si fermeranno (settle), ma il loro risultato sarà ignorato.

Promise.all non fa niente per cancellarle, perché nelle promise non esiste il concetto di “cancellazione”. In un altro capitolo copriremo AbortController il cui scopo è aiutarci ocn questo, but ma non è una pare delle API Promise.

Promise.all(...) accetta oggetti non-promise in un iterable

Normalmente, Promise.all(...) accetta un iterabile (nella maggior parte dei casi un array) di promises. Ma se uno qualsiasi di questi oggetti non è una promise, viene “avvolto” (wrapped) in Promise.resolve.

Per esempio, qui i risultati sono [1, 2, 3]: