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:
- Intro
- What Doesn't Wait for Await?
- Map Loops & Promise.all
- Map Will Not Be Sequential
- Sequential Loops With Reduce
- Showing Examples Without Logs
- Traditional Loop Behavior (for, while, do while)
- Key Takeaways
- Outro
📜 Transcript:
so one of the things that I've seen
people get tripped up on frequently is
how promises work within Loops because
there's two different types of loops and
two different ways that promises work
with them so you have the newer Loops
that have callbacks within them so like
for each map and reduce and then you
have the more traditional Loops like for
Loops while loops and do while Loops so
between those two newer and more
traditional Loop approaches promises
work in two different ways so we're
going to go over how exactly they work
within this lesson so for this I've set
up a simple command within an existing
adonisjs project so I have rows one
through five that we'll be creating
throughout our examples and I'm just
deleting out those example rules from
any previous command runs that we've
done through this example then I'm
logging out that we're starting our four
each loops and then I'm logging out that
we're ending our 4H Loop so we'll do all
of our work right here within this
comment block so we'll start with the
newer Loops since those are a little bit
easier to grasp visually so let's go
ahead and create all of our examples
here and we'll create them one by one
instead of using a create mini just so
that we can visualize this so one of the
things that I see people try to do is
they'll Loop over their examples so
they'll do example for each and they'll
have and async callback function for
each record and then they'll try to
await it within this callback and
they'll just go ahead and create the
record so if we add this logger info
starting
data.name and then this logger info
ending data.name we can see a little bit
easier what's happening so let's go
ahead and try to execute this by jumping
into our terminal and then we can run
our example here which is node A's
promise
and you can see we're greeted with an
error and that's because it's been
aborted so so there's a few different
reasons why you could get an aborted
error one of which is that you had some
pending actions or promises in our case
running still whenever your command
finished so if we scroll up just a
little bit here we can see our node Ace
promise execution we started our four
reach and then we got through each of
the ones within the loop so they all
started and then are for each finished
so visually what we can see here is that
our for each Loop is not listening to
our await at all and that behavior is
true for any of these callback approach
Loop methods so that would be for each
map reduce none of them will listen to
the await the individual line will await
so our ending call did execute after the
await finished but it did not wait
before moving on to the next Loop item
it just went about its business so one
of the ways that we can resolve the
abort error is by grabbing all of our
promises and making sure that they all
finished before we actually finish out
our Command so what I like to do for
that is to utilize the map method and
then we can utilize the map method to
return back promises to discern whether
or not the individual promise had
finished so we could do const promises
equals and then return back our promise
now since I'd like to visualize that the
promise did actually finish running here
we don't want to just return back so one
of the ways around this is that we can
create a custom promise so we can return
new promise async and get back our
resolve method here paste our contents
inside of here and then once we're done
with all of our actions we can just call
that resolve method to resolve this
promise that we're returning now
typescript's not happy with that so I'm
just going to go ahead and say that this
is any to make you happy so now for each
of our Maps we're returning back a
promise and then inside of our promise
we're running our execution and then
once we've run whatever it is that we
want to run we're resolving that promise
So within here we have all of our
promises and we can console log that as
well so we can do console.log here our
promises and then we also want to ensure
that they all finish before we finish
out our run command so we'll want to
awake eight promise and then you can do
all or all settled and then pass in your
promises and so this will return back an
array of values that you're actually
resolving from within your promise so if
we actually put our individual roll
records within here we would get those
back as an array and the value of our
all settled so for example if we do
const roll equals await this here would
be const rolls equals a weight if we can
console.log out those roles as well so
let's give that a save jump back into
our terminal
now let's try running that one more time
all right so you can see a couple of
different things here we started our
four each then we ran through each
individual Loop item starting each of
those promise calls and then we got back
an array of our promises you can see
each of these is in the state of pending
at this point in time and then we await
for each of those to end and then we get
back the resulting promise with the
value of each of those roles and the
status of fulfilled so we're no longer
running into an error issue and we're
now creating all of our roll records now
it just so happens to be due to this
being a Quick Command that they are
finishing in the same order that they
are starting this is just by chance
there is no promise of that actually
happening with this approach for example
if we wrap this inside of a set timeout
just like so and we grab the index of
our map and we take 1000 minus I times
100 so since these are quick commands
what I would expect to see here is that
that these will finish in the opposite
order of which they've started so let's
go ahead and give this a run I'm going
to go ahead and get rid of our
additional console logs here to make
this a little cleaner and then let's go
ahead and move our start out so that we
actually get an accurate depiction of
when this started so let's give that a
save there jump back into our terminal
give that a run
and you'll see it takes a little bit of
time here but we do get an end result so
we have roll one two three four five
starting and then we have five four
three two one ending so they ended in
the inverse order that they started just
as we expected since we're setting a
timeout inverting the amount of time
that the actual promise needs to await
so this is visualizing is that utilizing
map and for each with this approach
means that our array of promises are
going to execute and finish in any
random order depending on the amount of
time that the individual promise takes
to resolve so if you start a long
process as your first promise that
promise could end last if you need your
promises to execute sequentially so
starting and ending one starting and
ending two starting in ending three in
that type of order what I would
recommend doing for the Callback
approach here is to use reduce instead
okay so our accumulator for our reduce
is just going to be the previous promise
that we are awaiting so this will just
be a singular promise let's go ahead and
set this to any because typescript won't
be happy with that and then down down
here for our reduce start value we'll
want to just return new promise and then
let's have this resolve and then Auto
resolve so all that we're doing is
kicking this off with a default promise
that's just going to immediately resolve
and then we're passing that in as the
previous value So This Promise here will
just serve as a placeholder promise for
our default value of our accumulator so
what we want to do is await that promise
to finish and then once that does finish
we'll start with our new promise chain
and return that back so this actual
return value every time that we Loop
will serve as the previous promise which
will then await which will then return
back a new promise and then finally once
we return back the final promise all
we'll want to do is await that here and
then we no longer need to await for them
all to settle okay so once we have this
let's go ahead and give this a run and
this will take a second but you will
indeed see that we start Row one and
then end Row one and then start rule two
and then end roll two and then the same
with three four and five before a loop
finally finishes out so now each each of
our items are running sequentially and
that only takes a second because we have
our set timeout waiting for a portion of
a second so if we actually get rid of
this set timeout and for this we would
need to get rid of our ending console
log here and our resolve
take this down to just our role and
instead of awaiting this we can just
return back the promise that this
roll.create returns and get rid of our
wrapping promise and so now what we're
doing is We're looping over each of our
examples we're awaiting the previous
promise to finish and then we're
starting our new promise and returning
back the actual promise value to be our
new accumulator so although we won't
have our ending console log these will
execute in the same order that we had
seen in the example that we had just run
it will just run much quicker now since
we're no longer awaiting a portion of a
second so let's go and give this a run
now
and there you go we can see that was a
lot quicker and although we don't see
the end we know via this example that
they are now running in sequential order
so we have Row one two three four and
five before our Loop finishes out and
you can do the same approach with map as
well so if we switch this back to a map
get rid of that promise the only
difference is they won't run
sequentially but look at how clean we
can make this Loop so we'll get rid of
that we'll use the error function to
auto return and I will do const promises
equals await examples.map and then we'll
create each of those items and then we
can await promise dot all or all settled
and then provided in the premises so
although our previous examples were kind
of verbose since we had the additional
console logs in there look at how easy
this can come down to so we're down to
just two lines now we can execute this
and although we don't have any console
logs to show our work we know that since
we're using map unlike our resolve
example they're not going to run
sequentially so they'll start and end in
any order by how long the actual process
takes but we know that we're returning
back the promise that the roll dot
create is returning and we're awaiting
for each of those promises to finish out
before we actually move on lastly let's
talk about the more traditional Loops so
our for Loops while Loops they all
behave the exact same so they're all
going to actually await for our awaits
to execute so if we take our previous
example and put it inside of a for Loop
so for let I equal zero I is less than
examples.length and then I plus plus we
can follow our same naming structure and
do const data equals examples I and we
can do this logger info
starting
data.name this
logger info
ending data.name and then we can
actually come into here and await roll
create data and although for Loops May
trip people up with this particular line
right here I actually prefer them
whenever it comes to running promises
sequentially because we don't have to
worry about additional callbacks or
anything of the sort to await for things
to finalize we know that it's just going
to adhere to our await and we can read
things from top down knowing that
they're going to be run in a sequential
order so if we give this a save we can
see that by running this once more so
let's run today's promise and you can
see exactly what I just described so we
have start and end for Row one start and
end for roll two start and then for roll
three four and five and as I had
previously mentioned this is the same
behavior for any of these more
traditional Loops so whether you have
this for Loop whether you have the more
object base for Loop so four let key and
obj whether you have a do while loop so
something like a do while whether you
have just a while loop they're all going
to adhere to that await so they will all
behave in a similar fashion it's just
these callback based Loops that won't
await for our promise to finish so the
main takeaway would be that these
callback based Loops are fantastic if
you don't care about running your
promises sequentially you can simply do
this in two lines if your call is pretty
basic it's whenever you start to care
about sequential order of execution that
you want to start considering going back
to the more traditional Loops or
utilizing that reduced chain approach
that we had covered previously where you
await for the previous promise to
resolve before finally moving on to the
next one so hopefully that helps shed
some light on the order of execution
with promises within Loops the newer
callback bass loops and the more
traditional for Loops as you can see
there's just those two different
approaches to wrap your head around and
once you do you can actually use them to
your advantage pretty easily so thank
you for watching if you enjoyed or
learned something new consider hitting
that like button and subscribe button
down below and I'll see you in the next
one
[Music]
thank you
Join The Discussion! (0 Comments)
Please sign in or sign up for free to join in on the dicussion.
Be the first to Comment!