Understanding JavaScript Promises in ForEach, Map, and Reduce Loops

In this lesson, we'll take a look at how promises work when we try to await them inside newer callback-based loops, like forEach, map, and reduce.

Published
Nov 24, 22
Duration
11m 57s

Developer, dog lover, and burrito eater. Currently teaching AdonisJS, a fully featured NodeJS framework, and running Adocasts where I post new lessons weekly. Professionally, I work with JavaScript, .Net C#, and SQL Server.

Adocasts

Burlington, KY

In this lesson, we'll take a look at how promises work when we try to await them inside newer callback-based loops, like forEach, map, and reduce. We'll examine how we can use reduce to execute our promises sequentially. And, we'll see a quick way to await for all our promises to execute in a non-sequential order using map.

🥁 Subscribe To Stay Notified For New Lessons
📺 Watch on YouTube

📚 Chapters:
00:00 - Intro
00:53 - What Doesn't Wait for Await?
02:18 - Map Loops & Promise.all
04:30 - Map Will Not Be Sequential
05:50 - Sequential Loops With Reduce
07:20 - Showing Examples Without Logs
09:10 - Traditional Loop Behavior (for, while, do while)
10:50 - Key Takeaways
11:19 - Outro

📜 Transcript:

0:00

so one of the things that I've seen

0:01

people get tripped up on frequently is

0:02

how promises work within Loops because

0:04

there's two different types of loops and

0:06

two different ways that promises work

0:07

with them so you have the newer Loops

0:08

that have callbacks within them so like

0:10

for each map and reduce and then you

0:12

have the more traditional Loops like for

0:13

Loops while loops and do while Loops so

0:15

between those two newer and more

0:17

traditional Loop approaches promises

0:19

work in two different ways so we're

0:21

going to go over how exactly they work

0:22

within this lesson so for this I've set

0:24

up a simple command within an existing

0:25

adonisjs project so I have rows one

0:27

through five that we'll be creating

0:28

throughout our examples and I'm just

0:30

deleting out those example rules from

0:31

any previous command runs that we've

0:33

done through this example then I'm

0:34

logging out that we're starting our four

0:36

each loops and then I'm logging out that

0:37

we're ending our 4H Loop so we'll do all

0:39

of our work right here within this

0:40

comment block so we'll start with the

0:42

newer Loops since those are a little bit

0:43

easier to grasp visually so let's go

0:46

ahead and create all of our examples

0:47

here and we'll create them one by one

0:49

instead of using a create mini just so

0:51

that we can visualize this so one of the

0:53

things that I see people try to do is

0:55

they'll Loop over their examples so

0:57

they'll do example for each and they'll

1:00

have and async callback function for

1:03

each record and then they'll try to

1:04

await it within this callback and

1:06

they'll just go ahead and create the

1:08

record so if we add this logger info

1:12

starting

1:13

data.name and then this logger info

1:17

ending data.name we can see a little bit

1:21

easier what's happening so let's go

1:22

ahead and try to execute this by jumping

1:24

into our terminal and then we can run

1:25

our example here which is node A's

1:27

promise

1:28

and you can see we're greeted with an

1:30

error and that's because it's been

1:31

aborted so so there's a few different

1:33

reasons why you could get an aborted

1:34

error one of which is that you had some

1:36

pending actions or promises in our case

1:38

running still whenever your command

1:40

finished so if we scroll up just a

1:42

little bit here we can see our node Ace

1:43

promise execution we started our four

1:45

reach and then we got through each of

1:47

the ones within the loop so they all

1:49

started and then are for each finished

1:51

so visually what we can see here is that

1:54

our for each Loop is not listening to

1:57

our await at all and that behavior is

1:59

true for any of these callback approach

2:01

Loop methods so that would be for each

2:04

map reduce none of them will listen to

2:06

the await the individual line will await

2:08

so our ending call did execute after the

2:11

await finished but it did not wait

2:13

before moving on to the next Loop item

2:15

it just went about its business so one

2:17

of the ways that we can resolve the

2:18

abort error is by grabbing all of our

2:21

promises and making sure that they all

2:22

finished before we actually finish out

2:24

our Command so what I like to do for

2:26

that is to utilize the map method and

2:28

then we can utilize the map method to

2:29

return back promises to discern whether

2:31

or not the individual promise had

2:33

finished so we could do const promises

2:35

equals and then return back our promise

2:37

now since I'd like to visualize that the

2:40

promise did actually finish running here

2:42

we don't want to just return back so one

2:44

of the ways around this is that we can

2:46

create a custom promise so we can return

2:47

new promise async and get back our

2:51

resolve method here paste our contents

2:53

inside of here and then once we're done

2:55

with all of our actions we can just call

2:57

that resolve method to resolve this

2:59

promise that we're returning now

3:00

typescript's not happy with that so I'm

3:01

just going to go ahead and say that this

3:03

is any to make you happy so now for each

3:05

of our Maps we're returning back a

3:06

promise and then inside of our promise

3:09

we're running our execution and then

3:11

once we've run whatever it is that we

3:13

want to run we're resolving that promise

3:15

So within here we have all of our

3:17

promises and we can console log that as

3:19

well so we can do console.log here our

3:21

promises and then we also want to ensure

3:24

that they all finish before we finish

3:26

out our run command so we'll want to

3:27

awake eight promise and then you can do

3:30

all or all settled and then pass in your

3:33

promises and so this will return back an

3:34

array of values that you're actually

3:36

resolving from within your promise so if

3:38

we actually put our individual roll

3:40

records within here we would get those

3:41

back as an array and the value of our

3:43

all settled so for example if we do

3:45

const roll equals await this here would

3:48

be const rolls equals a weight if we can

3:50

console.log out those roles as well so

3:53

let's give that a save jump back into

3:54

our terminal

3:56

now let's try running that one more time

3:57

all right so you can see a couple of

3:59

different things here we started our

4:00

four each then we ran through each

4:02

individual Loop item starting each of

4:04

those promise calls and then we got back

4:07

an array of our promises you can see

4:09

each of these is in the state of pending

4:11

at this point in time and then we await

4:13

for each of those to end and then we get

4:15

back the resulting promise with the

4:18

value of each of those roles and the

4:20

status of fulfilled so we're no longer

4:23

running into an error issue and we're

4:25

now creating all of our roll records now

4:28

it just so happens to be due to this

4:30

being a Quick Command that they are

4:33

finishing in the same order that they

4:35

are starting this is just by chance

4:36

there is no promise of that actually

4:38

happening with this approach for example

4:40

if we wrap this inside of a set timeout

4:43

just like so and we grab the index of

4:47

our map and we take 1000 minus I times

4:52

100 so since these are quick commands

4:54

what I would expect to see here is that

4:55

that these will finish in the opposite

4:58

order of which they've started so let's

5:00

go ahead and give this a run I'm going

5:01

to go ahead and get rid of our

5:02

additional console logs here to make

5:04

this a little cleaner and then let's go

5:05

ahead and move our start out so that we

5:07

actually get an accurate depiction of

5:09

when this started so let's give that a

5:11

save there jump back into our terminal

5:13

give that a run

5:15

and you'll see it takes a little bit of

5:16

time here but we do get an end result so

5:19

we have roll one two three four five

5:21

starting and then we have five four

5:23

three two one ending so they ended in

5:26

the inverse order that they started just

5:28

as we expected since we're setting a

5:29

timeout inverting the amount of time

5:31

that the actual promise needs to await

5:32

so this is visualizing is that utilizing

5:35

map and for each with this approach

5:37

means that our array of promises are

5:40

going to execute and finish in any

5:42

random order depending on the amount of

5:43

time that the individual promise takes

5:45

to resolve so if you start a long

5:47

process as your first promise that

5:49

promise could end last if you need your

5:52

promises to execute sequentially so

5:54

starting and ending one starting and

5:55

ending two starting in ending three in

5:57

that type of order what I would

5:59

recommend doing for the Callback

6:01

approach here is to use reduce instead

6:03

okay so our accumulator for our reduce

6:06

is just going to be the previous promise

6:07

that we are awaiting so this will just

6:10

be a singular promise let's go ahead and

6:11

set this to any because typescript won't

6:13

be happy with that and then down down

6:15

here for our reduce start value we'll

6:17

want to just return new promise and then

6:20

let's have this resolve and then Auto

6:22

resolve so all that we're doing is

6:24

kicking this off with a default promise

6:26

that's just going to immediately resolve

6:27

and then we're passing that in as the

6:30

previous value So This Promise here will

6:31

just serve as a placeholder promise for

6:34

our default value of our accumulator so

6:36

what we want to do is await that promise

6:39

to finish and then once that does finish

6:41

we'll start with our new promise chain

6:43

and return that back so this actual

6:45

return value every time that we Loop

6:47

will serve as the previous promise which

6:49

will then await which will then return

6:51

back a new promise and then finally once

6:53

we return back the final promise all

6:55

we'll want to do is await that here and

6:58

then we no longer need to await for them

7:00

all to settle okay so once we have this

7:02

let's go ahead and give this a run and

7:03

this will take a second but you will

7:05

indeed see that we start Row one and

7:06

then end Row one and then start rule two

7:08

and then end roll two and then the same

7:10

with three four and five before a loop

7:13

finally finishes out so now each each of

7:15

our items are running sequentially and

7:17

that only takes a second because we have

7:18

our set timeout waiting for a portion of

7:20

a second so if we actually get rid of

7:22

this set timeout and for this we would

7:24

need to get rid of our ending console

7:26

log here and our resolve

7:29

take this down to just our role and

7:32

instead of awaiting this we can just

7:33

return back the promise that this

7:34

roll.create returns and get rid of our

7:37

wrapping promise and so now what we're

7:39

doing is We're looping over each of our

7:41

examples we're awaiting the previous

7:43

promise to finish and then we're

7:45

starting our new promise and returning

7:47

back the actual promise value to be our

7:49

new accumulator so although we won't

7:52

have our ending console log these will

7:54

execute in the same order that we had

7:56

seen in the example that we had just run

7:58

it will just run much quicker now since

8:00

we're no longer awaiting a portion of a

8:02

second so let's go and give this a run

8:03

now

8:05

and there you go we can see that was a

8:06

lot quicker and although we don't see

8:08

the end we know via this example that

8:10

they are now running in sequential order

8:12

so we have Row one two three four and

8:15

five before our Loop finishes out and

8:18

you can do the same approach with map as

8:19

well so if we switch this back to a map

8:21

get rid of that promise the only

8:23

difference is they won't run

8:24

sequentially but look at how clean we

8:26

can make this Loop so we'll get rid of

8:28

that we'll use the error function to

8:31

auto return and I will do const promises

8:34

equals await examples.map and then we'll

8:36

create each of those items and then we

8:38

can await promise dot all or all settled

8:42

and then provided in the premises so

8:43

although our previous examples were kind

8:45

of verbose since we had the additional

8:46

console logs in there look at how easy

8:48

this can come down to so we're down to

8:50

just two lines now we can execute this

8:52

and although we don't have any console

8:53

logs to show our work we know that since

8:55

we're using map unlike our resolve

8:57

example they're not going to run

8:58

sequentially so they'll start and end in

9:00

any order by how long the actual process

9:01

takes but we know that we're returning

9:03

back the promise that the roll dot

9:05

create is returning and we're awaiting

9:07

for each of those promises to finish out

9:09

before we actually move on lastly let's

9:12

talk about the more traditional Loops so

9:14

our for Loops while Loops they all

9:16

behave the exact same so they're all

9:18

going to actually await for our awaits

9:21

to execute so if we take our previous

9:23

example and put it inside of a for Loop

9:24

so for let I equal zero I is less than

9:29

examples.length and then I plus plus we

9:32

can follow our same naming structure and

9:34

do const data equals examples I and we

9:38

can do this logger info

9:41

starting

9:42

data.name this

9:44

logger info

9:46

ending data.name and then we can

9:49

actually come into here and await roll

9:52

create data and although for Loops May

9:55

trip people up with this particular line

9:58

right here I actually prefer them

9:59

whenever it comes to running promises

10:00

sequentially because we don't have to

10:02

worry about additional callbacks or

10:03

anything of the sort to await for things

10:05

to finalize we know that it's just going

10:07

to adhere to our await and we can read

10:10

things from top down knowing that

10:11

they're going to be run in a sequential

10:13

order so if we give this a save we can

10:15

see that by running this once more so

10:17

let's run today's promise and you can

10:20

see exactly what I just described so we

10:22

have start and end for Row one start and

10:24

end for roll two start and then for roll

10:25

three four and five and as I had

10:28

previously mentioned this is the same

10:30

behavior for any of these more

10:32

traditional Loops so whether you have

10:34

this for Loop whether you have the more

10:36

object base for Loop so four let key and

10:39

obj whether you have a do while loop so

10:42

something like a do while whether you

10:45

have just a while loop they're all going

10:48

to adhere to that await so they will all

10:50

behave in a similar fashion it's just

10:52

these callback based Loops that won't

10:54

await for our promise to finish so the

10:56

main takeaway would be that these

10:57

callback based Loops are fantastic if

10:59

you don't care about running your

11:00

promises sequentially you can simply do

11:03

this in two lines if your call is pretty

11:05

basic it's whenever you start to care

11:07

about sequential order of execution that

11:09

you want to start considering going back

11:11

to the more traditional Loops or

11:12

utilizing that reduced chain approach

11:14

that we had covered previously where you

11:15

await for the previous promise to

11:16

resolve before finally moving on to the

11:19

next one so hopefully that helps shed

11:20

some light on the order of execution

11:22

with promises within Loops the newer

11:24

callback bass loops and the more

11:26

traditional for Loops as you can see

11:27

there's just those two different

11:28

approaches to wrap your head around and

11:30

once you do you can actually use them to

11:31

your advantage pretty easily so thank

11:33

you for watching if you enjoyed or

11:34

learned something new consider hitting

11:35

that like button and subscribe button

11:37

down below and I'll see you in the next

11:38

one

11:43

[Music]

11:49

thank you

Join The Discussion! (0 Comments)

Please sign in or sign up for free to join in on the dicussion.

robot comment bubble

Be the first to Comment!

Playing Next Lesson In
seconds