forEach/map e async non vanno di pari passo

A volte, tendiamo spesso a passare funzioni asincrone con funzioni forEach e map.
Per es:
[1,2,3,4,5].forEach(async (n) => setTimeout(console.log(n), 1000));
Risultato: tutti i numeri da 1 a 5 vengono stampati uno dopo l'altro ma non c'è intervallo di 1 secondo dopo la stampa di ciascuna riga.
Perché succede?
Guarda questa funzione javascript:
async function doubleOf(n) {
return n*2;
}
Risultato: questo in realtà restituisce una Promessa che si risolve in un numero.
Se scriviamo un equivalente Typescript di questa funzione con tipi rigorosi, chiarirà le cose.
Il seguente codice non verrà compilato:
async function doubleOf(n: number): number {
return n*2;
}
La versione corretta sarebbe:
async function doubleOf(n: number): Promise<number> {
return n*2;
}
Non lasciarti ingannare dallo zucchero sintattico fornito da async-await. Se scrivessimo promesse pure senza usare async, la funzione sopra sarebbe simile a:
function doubleOf(n) {
return new Promise((resolve) => resolve(n*2));
}
function doubleOf(n: number): Promise<number> {
return new Promise((resolve) => resolve(n*2));
}

- Abbiamo
doubleOf
una funzione che accetta un numero e restituisce un numero. Semplice vecchio javascript. - Abbiamo
doubleOfOldWay
una funzione che accetta un numero e restituisce una promessa che si risolve in un numero. - Abbiamo
doubleOfNewWay
, una funzione asincrona che accetta un numero e sembra che restituisca un numero, ma in realtà restituisce una promessa che si risolve in un numero proprio come ladoubleOfOldWay
funzione. doubleOfOldWay
edoubleOfNewWay
le funzioni sono esattamente le stesse.- E quindi, quando proviamo ad eseguire un'operazione di moltiplicazione sui valori restituiti da
doubleOfOldWay
edoubleOfNewWay
funzioni, il risultato è statoNaN
, poiché non possiamo più promesse (ovviamente!). - Moltiplicare
doubleOfOldWay
edoubleOfNewWay
:
Quindi torniamo al nostro esempio iniziale:
[1,2,3,4,5].forEach(async (n) => setTimeout(console.log(n), 1000));
Il modo più corretto per implementare ciò che ci aspettiamo da questa funzione forEach è usare un semplice ciclo for:
for(const number of [1,2,3,4,5]) {
console.log(number);
await new Promise(resolve => setTimeout(resolve, 1000)); // Sleep for "atleast" 1 second
}
[1,2,3,4,5].map(async (n) => n*2);
(5) [Promise, Promise, Promise, Promise, Promise]
0: Promise {<fulfilled>: 2}
1: Promise {<fulfilled>: 4}
2: Promise {<fulfilled>: 6}
3: Promise {<fulfilled>: 8}
4: Promise {<fulfilled>: 10}
Per ottenere un elenco di doppio di ogni numero, quello che possiamo fare è:
await Promise.all([1,2,3,4,5].map(async (n) => n*2));
[2, 4, 6, 8, 10]