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!