Osnovno async
i await
jednostavno. Stvari se malo zakompliciraju kada pokušate koristiti await
petlje.
U ovom članku želim podijeliti neke probleme na koje trebate pripaziti ako namjeravate koristiti await
petlje.
Prije nego što počneš
Pretpostavit ću da znate kako koristiti async
i await
. Ako ne, pročitajte prethodni članak kako biste se upoznali prije nego što nastavite.
Priprema primjera
Za ovaj članak recimo da želite dobiti broj voća iz košarice s voćem.
const fruitBasket = { apple: 27, grape: 0, pear: 14 };
Želite dobiti broj svakog voća iz fruitcoasketa. Da biste dobili broj ploda, možete koristiti getNumFruit
funkciju.
const getNumFruit = fruit => { return fruitBasket[fruit]; }; const numApples = getNumFruit(“apple”); console.log(numApples); // 27
Recimo sada da fruitBasket
živi na udaljenom poslužitelju. Pristup joj traje jednu sekundu. Ovom kašnjenju od jedne sekunde možemo se rugati s vremenskim ograničenjem. (Molimo pogledajte prethodni članak ako imate problema s razumijevanjem vremenskog ograničenja).
const sleep = ms => { return new Promise(resolve => setTimeout(resolve, ms)); }; const getNumFruit = fruit => { return sleep(1000).then(v => fruitBasket[fruit]); }; getNumFruit(“apple”).then(num => console.log(num)); // 27
Na kraju, recimo da želite koristiti await
i getNumFruit
dobiti broj svakog voća u asinkronoj funkciji.
const control = async _ => { console.log(“Start”); const numApples = await getNumFruit(“apple”); console.log(numApples); const numGrapes = await getNumFruit(“grape”); console.log(numGrapes); const numPears = await getNumFruit(“pear”); console.log(numPears); console.log(“End”); };

S ovim možemo početi gledati await
u petlje.
Čekajte for for petlju
Recimo da imamo niz voća koje želimo dobiti iz košarice s voćem.
const fruitsToGet = [“apple”, “grape”, “pear”];
Krenut ćemo kroz ovaj niz.
const forLoop = async _ => { console.log(“Start”); for (let index = 0; index < fruitsToGet.length; index++) { // Get num of each fruit } console.log(“End”); };
U petlji for koristit ćemo getNumFruit
za dobivanje broja svakog voća. Također ćemo prijaviti broj u konzolu.
Budući da getNumFruit
vraća obećanje, možemo await
riješiti vrijednost prije prijave.
const forLoop = async _ => { console.log(“Start”); for (let index = 0; index < fruitsToGet.length; index++) { const fruit = fruitsToGet[index]; const numFruit = await getNumFruit(fruit); console.log(numFruit); } console.log(“End”); };
Kada upotrebljavate await
, očekujete da JavaScript zaustavi izvršenje dok se očekivano obećanje ne riješi. To znači da se await
s u for-loop treba izvoditi u seriji.
Rezultat je ono što biste očekivali.
“Start”; “Apple: 27”; “Grape: 0”; “Pear: 14”; “End”;

Ovo ponašanje radi s većinom petlji (poput while
i for-of
petlji) ...
Ali neće raditi s petljama koje zahtijevaju povratni poziv. Primjeri takvih petlji koje zahtijevaju vraćanje uključuju forEach
, map
, filter
, i reduce
. Mi ćemo pogledati kako await
utječe forEach
, map
i filter
u sljedećih nekoliko dijelova.
Čekajte u forEach petlji
Učinit ćemo isto što i u primjeru for-loop. Prvo, krenimo kroz niz plodova.
const forEachLoop = _ => { console.log(“Start”); fruitsToGet.forEach(fruit => { // Send a promise for each fruit }); console.log(“End”); };
Dalje, pokušat ćemo dobiti broj voća getNumFruit
. (Primijetite async
ključnu riječ u funkciji povratnog poziva. Ova nam async
ključna riječ treba jer await
je u funkciji povratnog poziva).
const forEachLoop = _ => { console.log(“Start”); fruitsToGet.forEach(async fruit => { const numFruit = await getNumFruit(fruit); console.log(numFruit); }); console.log(“End”); };
Mogli biste očekivati da konzola izgleda ovako:
“Start”; “27”; “0”; “14”; “End”;
Ali stvarni rezultat je drugačiji. JavaScript nastavlja s pozivom console.log('End')
prije nego što se riješe obećanja u forEach petlji.
Konzola se zapisuje ovim redoslijedom:
‘Start’ ‘End’ ‘27’ ‘0’ ‘14’

JavaScript to čini jer forEach
nije svjestan obećanja. Ne može podržati async
i await
. Ne možete _ koristiti await
u forEach
.
Čekajte s mapom
Ako koristite await
u map
, map
uvijek će vratiti niz obećanja. To je zato što asinkrone funkcije uvijek vraćaju obećanja.
const mapLoop = async _ => { console.log(“Start”); const numFruits = await fruitsToGet.map(async fruit => { const numFruit = await getNumFruit(fruit); return numFruit; }); console.log(numFruits); console.log(“End”); }; “Start”; “[Promise, Promise, Promise]”; “End”;

Budući da map
uvijek vraćate obećanja (ako koristite await
), morate pričekati da se niz obećanja riješi. To možete učiniti s await Promise.all(arrayOfPromises)
.
const mapLoop = async _ => { console.log(“Start”); const promises = fruitsToGet.map(async fruit => { const numFruit = await getNumFruit(fruit); return numFruit; }); const numFruits = await Promise.all(promises); console.log(numFruits); console.log(“End”); };
Evo što dobivate:
“Start”; “[27, 0, 14]”; “End”;

Ako želite, možete manipulirati vrijednošću koju vraćate u svojim obećanjima. Razriješene vrijednosti bit će vrijednosti koje ste vratili.
const mapLoop = async _ => { // … const promises = fruitsToGet.map(async fruit => { const numFruit = await getNumFruit(fruit); // Adds onn fruits before returning return numFruit + 100; }); // … }; “Start”; “[127, 100, 114]”; “End”;
Čekajte s filtrom
Kada upotrebljavate filter
, želite filtrirati niz s određenim rezultatom. Recimo da želite stvoriti niz s više od 20 plodova.
Ako upotrebljavate filter
normalno (bez čekanja), upotrijebit ćete ga ovako:
// Filter if there’s no await const filterLoop = _ => { console.log(‘Start’) const moreThan20 = await fruitsToGet.filter(fruit => { const numFruit = fruitBasket[fruit] return numFruit > 20 }) console.log(moreThan20) console.log(‘End’) }
Očekivali biste moreThan20
da sadrži samo jabuke jer ima 27 jabuka, ali ima 0 grožđa i 14 krušaka.
“Start”[“apple”]; (“End”);
await
u filter
ne radi na isti način. Zapravo uopće ne ide. Vratit ćete nefiltrirani niz ...
const filterLoop = _ => { console.log(‘Start’) const moreThan20 = await fruitsToGet.filter(async fruit => { const numFruit = getNumFruit(fruit) return numFruit > 20 }) console.log(moreThan20) console.log(‘End’) } “Start”[(“apple”, “grape”, “pear”)]; (“End”);

Here's why it happens.
When you use await
in a filter
callback, the callback always a promise. Since promises are always truthy, everything item in the array passes the filter. Writing await
in a filter
is like writing this code:
// Everything passes the filter… const filtered = array.filter(true);
There are three steps to use await
and filter
properly:
1. Use map
to return an array promises
2. await
the array of promises
3. filter
the resolved values
const filterLoop = async _ => { console.log(“Start”); const promises = await fruitsToGet.map(fruit => getNumFruit(fruit)); const numFruits = await Promise.all(promises); const moreThan20 = fruitsToGet.filter((fruit, index) => { const numFruit = numFruits[index]; return numFruit > 20; }); console.log(moreThan20); console.log(“End”); }; Start[“apple”]; End;

Await with reduce
For this case, let's say you want to find out the total number of fruits in the fruitBastet. Normally, you can use reduce
to loop through an array and sum the number up.
// Reduce if there’s no await const reduceLoop = _ => { console.log(“Start”); const sum = fruitsToGet.reduce((sum, fruit) => { const numFruit = fruitBasket[fruit]; return sum + numFruit; }, 0); console.log(sum); console.log(“End”); };
You'll get a total of 41 fruits. (27 + 0 + 14 = 41).
“Start”; “41”; “End”;

When you use await
with reduce, the results get extremely messy.
// Reduce if we await getNumFruit const reduceLoop = async _ => { console.log(“Start”); const sum = await fruitsToGet.reduce(async (sum, fruit) => { const numFruit = await getNumFruit(fruit); return sum + numFruit; }, 0); console.log(sum); console.log(“End”); }; “Start”; “[object Promise]14”; “End”;

What?! [object Promise]14
?!
Dissecting this is interesting.
- In the first iteration,
sum
is0
.numFruit
is 27 (the resolved value fromgetNumFruit(‘apple’)
).0 + 27
is 27. - In the second iteration,
sum
is a promise. (Why? Because asynchronous functions always return promises!)numFruit
is 0. A promise cannot be added to an object normally, so the JavaScript converts it to[object Promise]
string.[object Promise] + 0
is[object Promise]0
- In the third iteration,
sum
is also a promise.numFruit
is14
.[object Promise] + 14
is[object Promise]14
.
Mystery solved!
This means, you can use await
in a reduce
callback, but you have to remember to await
the accumulator first!
const reduceLoop = async _ => { console.log(“Start”); const sum = await fruitsToGet.reduce(async (promisedSum, fruit) => { const sum = await promisedSum; const numFruit = await getNumFruit(fruit); return sum + numFruit; }, 0); console.log(sum); console.log(“End”); }; “Start”; “41”; “End”;

But... as you can see from the gif, it takes pretty long to await
everything. This happens because reduceLoop
needs to wait for the promisedSum
to be completed for each iteration.
There's a way to speed up the reduce loop. (I found out about this thanks to Tim Oxley. If you await getNumFruits(
) first before await promisedSum
, the reduceLoop
takes only one second to complete:
const reduceLoop = async _ => { console.log(“Start”); const sum = await fruitsToGet.reduce(async (promisedSum, fruit) => { // Heavy-lifting comes first. // This triggers all three getNumFruit promises before waiting for the next iteration of the loop. const numFruit = await getNumFruit(fruit); const sum = await promisedSum; return sum + numFruit; }, 0); console.log(sum); console.log(“End”); };

This works because reduce
can fire all three getNumFruit
promises before waiting for the next iteration of the loop. However, this method is slightly confusing since you have to be careful of the order you await
things.
The simplest (and most efficient way) to use await
in reduce is to:
1. Use map
to return an array promises
2. await
the array of promises
3. reduce
the resolved values
const reduceLoop = async _ => { console.log(“Start”); const promises = fruitsToGet.map(getNumFruit); const numFruits = await Promise.all(promises); const sum = numFruits.reduce((sum, fruit) => sum + fruit); console.log(sum); console.log(“End”); };
This version is simple to read and understand, and takes one second to calculate the total number of fruits.

Key Takeaways
1. If you want to execute await
calls in series, use a for-loop
(or any loop without a callback).
2. Don't ever use await
with forEach
. Use a for-loop
(or any loop without a callback) instead.
3. Don't await
inside filter
and reduce
. Always await
an array of promises with map
, then filter
or reduce
accordingly.
This article was originally posted on my blog.
Sign up for my newsletter if you want more articles to help you become a better frontend developer.