forEach/map i async nie idą w parze
Czasami często przekazujemy funkcje asynchroniczne z funkcjami forEach i map.
na przykład:
[1,2,3,4,5].forEach(async (n) => setTimeout(console.log(n), 1000));
Wynik: Wszystkie liczby od 1 do 5 są drukowane jedna po drugiej, alepo wydrukowaniu każdej linii nie ma 1-sekundowej przerwy .
Dlaczego to się dzieje?
Spójrz na tę funkcję javascipt:
async function doubleOf(n) {
return n*2;
}
Wynik: W rzeczywistości jest to zwrócenie Obietnicy, która rozwiązuje się na liczbę.
Jeśli napiszemy odpowiednik tej funkcji w maszynopisie z typami ścisłymi, wszystko stanie się jasne.
Poniższy kod nie skompiluje się:
async function doubleOf(n: number): number {
return n*2;
}
Poprawna wersja to:
async function doubleOf(n: number): Promise<number> {
return n*2;
}
Nie daj się nabrać na syntaktyczny cukier dostarczany przez async-await. Gdybyśmy napisali czyste obietnice bez użycia asynchronizacji, powyższa funkcja wyglądałaby następująco:
function doubleOf(n) {
return new Promise((resolve) => resolve(n*2));
}
function doubleOf(n: number): Promise<number> {
return new Promise((resolve) => resolve(n*2));
}
- Mamy
doubleOf
funkcję, która pobiera liczbę i zwraca liczbę. Zwykły stary javascript. - Mamy
doubleOfOldWay
funkcję, która przyjmuje liczbę i zwraca obietnicę, która rozwiązuje się na liczbę. - Mamy
doubleOfNewWay
funkcję asynchroniczną, która przyjmuje liczbę i wygląda na to, że zwraca liczbę, ale w rzeczywistości zwraca obietnicę, która rozwiązuje się na liczbę, tak jakdoubleOfOldWay
funkcja. doubleOfOldWay
idoubleOfNewWay
funkcje są dokładnie takie same.- I stąd, gdy próbujemy wykonać operację mnożenia na wartościach zwracanych przez funkcje
doubleOfOldWay
idoubleOfNewWay
, wynikiem jestNaN
, ponieważ nie możemy składać wielu obietnic (oczywiście!). - Aby pomnożyć
doubleOfOldWay
idoubleOfNewWay
:
Wróćmy więc do naszego początkowego przykładu:
[1,2,3,4,5].forEach(async (n) => setTimeout(console.log(n), 1000));
Najbardziej poprawnym sposobem zaimplementowania tego, czego oczekujemy od tej funkcji forEach, jest użycie prostej pętli 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}
Aby uzyskać listę podwójnych wartości każdej liczby, możemy:
await Promise.all([1,2,3,4,5].map(async (n) => n*2));
[2, 4, 6, 8, 10]